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 # for security, this is a process-defined and not
29 # a client-defined variable
32 # ------------------------------------------------------------------------------
33 # Load the circ script from the config
34 # ------------------------------------------------------------------------------
38 $cache_handle = OpenSRF::Utils::Cache->new('global');
39 my $conf = OpenSRF::Utils::SettingsClient->new;
40 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
41 my @pfx = ( @pfx2, "scripts" );
43 my $p = $conf->config_value( @pfx, 'circ_permit_patron' );
44 my $c = $conf->config_value( @pfx, 'circ_permit_copy' );
45 my $d = $conf->config_value( @pfx, 'circ_duration' );
46 my $f = $conf->config_value( @pfx, 'circ_recurring_fines' );
47 my $m = $conf->config_value( @pfx, 'circ_max_fines' );
48 my $pr = $conf->config_value( @pfx, 'circ_permit_renew' );
49 my $ph = $conf->config_value( @pfx, 'circ_permit_hold' );
50 my $lb = $conf->config_value( @pfx2, 'script_path' );
52 $logger->error( "Missing circ script(s)" )
53 unless( $p and $c and $d and $f and $m and $pr and $ph );
55 $scripts{circ_permit_patron} = $p;
56 $scripts{circ_permit_copy} = $c;
57 $scripts{circ_duration} = $d;
58 $scripts{circ_recurring_fines}= $f;
59 $scripts{circ_max_fines} = $m;
60 $scripts{circ_permit_renew} = $pr;
61 $scripts{hold_permit_permit} = $ph;
63 $lb = [ $lb ] unless ref($lb);
66 $logger->debug("Loaded rules scripts for circ: " .
67 "circ permit patron: $p, circ permit copy: $c, ".
68 "circ duration :$d , circ recurring fines : $f, " .
69 "circ max fines : $m, circ renew permit : $pr, permit hold: $ph");
73 # ------------------------------------------------------------------------------
74 # Loads the necessary circ objects and pushes them into the script environment
75 # Returns ( $data, $evt ). if $evt is defined, then an
76 # unexpedted event occurred and should be dealt with / returned to the caller
77 # ------------------------------------------------------------------------------
85 $evt = _ctx_add_patron_objects($ctx, %params);
86 return (undef,$evt) if $evt;
88 if(!$params{noncat}) {
89 if( $evt = _ctx_add_copy_objects($ctx, %params) ) {
90 $ctx->{precat} = 1 if($evt->{textcode} eq 'COPY_NOT_FOUND')
92 $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
96 _doctor_patron_object($ctx) if $ctx->{patron};
97 _doctor_copy_object($ctx) if $ctx->{copy};
99 if(!$ctx->{no_runner}) {
100 _build_circ_script_runner($ctx);
101 _add_script_runner_methods($ctx);
107 sub _ctx_add_patron_objects {
108 my( $ctx, %params) = @_;
111 if(!defined($cache{patron_standings})) {
112 $cache{patron_standings} = $U->fetch_patron_standings();
113 $cache{group_tree} = $U->fetch_permission_group_tree();
116 $ctx->{patron_standings} = $cache{patron_standings};
117 $ctx->{group_tree} = $cache{group_tree};
119 $ctx->{patron_circ_summary} =
120 $U->fetch_patron_circ_summary($ctx->{patron}->id)
121 if $params{fetch_patron_circsummary};
127 sub _find_copy_by_attr {
132 my $copy = $params{copy} || undef;
137 $U->fetch_copy($params{copyid}) if $params{copyid};
138 return (undef,$evt) if $evt;
142 $U->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
143 return (undef,$evt) if $evt;
146 return ( $copy, $evt );
149 sub _ctx_add_copy_objects {
150 my($ctx, %params) = @_;
155 $cache{copy_statuses} = $U->fetch_copy_statuses
156 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
158 $cache{copy_locations} = $U->fetch_copy_locations
159 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
161 $ctx->{copy_statuses} = $cache{copy_statuses};
162 $ctx->{copy_locations} = $cache{copy_locations};
164 ($copy, $evt) = _find_copy_by_attr(%params);
168 $logger->debug("Copy status: " . $copy->status);
169 ( $ctx->{title}, $evt ) = $U->fetch_record_by_copy( $copy->id );
171 $ctx->{copy} = $copy;
178 # ------------------------------------------------------------------------------
179 # Fleshes parts of the patron object
180 # ------------------------------------------------------------------------------
181 sub _doctor_copy_object {
184 my $copy = $ctx->{copy} || return undef;
186 $logger->debug("Doctoring copy object...");
188 # set the copy status to a status name
189 $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
191 # set the copy location to the location object
192 $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
194 $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
198 # ------------------------------------------------------------------------------
199 # Fleshes parts of the patron object
200 # ------------------------------------------------------------------------------
201 sub _doctor_patron_object {
204 my $patron = $ctx->{patron} || return undef;
206 # push the standing object into the patron
207 if(ref($ctx->{patron_standings})) {
208 for my $s (@{$ctx->{patron_standings}}) {
209 if( $s->id eq $ctx->{patron}->standing ) {
210 $patron->standing($s);
211 $logger->debug("Set patron standing to ". $s->value);
216 # set the patron ptofile to the profile name
217 $patron->profile( _get_patron_profile(
218 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
222 $U->fetch_org_unit( $patron->home_ou ) ) if $patron;
226 # recurse and find the patron profile name from the tree
227 # another option would be to grab the groups for the patron
228 # and cycle through those until the "profile" group has been found
229 sub _get_patron_profile {
230 my( $patron, $group_tree ) = @_;
231 return $group_tree if ($group_tree->id eq $patron->profile);
232 return undef unless ($group_tree->children);
234 for my $child (@{$group_tree->children}) {
235 my $ret = _get_patron_profile( $patron, $child );
241 sub _get_copy_status {
242 my( $copy, $cstatus ) = @_;
245 for my $status (@$cstatus) {
246 $s = $status if( $status->id eq $copy->status )
248 $logger->debug("Retrieving copy status: " . $s->name) if $s;
252 sub _get_copy_location {
253 my( $copy, $locations ) = @_;
256 for my $loc (@$locations) {
257 $l = $loc if $loc->id eq $copy->location;
259 $logger->debug("Retrieving copy location: " . $l->name ) if $l;
264 # ------------------------------------------------------------------------------
265 # Constructs and shoves data into the script environment
266 # ------------------------------------------------------------------------------
267 sub _build_circ_script_runner {
271 $logger->debug("Loading script environment for circulation");
274 if( $runner = $contexts{$ctx->{type}} ) {
275 $runner->refresh_context;
277 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
278 $contexts{type} = $runner;
282 $logger->debug("Loading circ script lib path $_");
283 $runner->add_path( $_ );
287 $runner->insert( 'environment.patron', $ctx->{patron}, 1);
288 $runner->insert( 'environment.title', $ctx->{title}, 1);
289 $runner->insert( 'environment.copy', $ctx->{copy}, 1);
292 $runner->insert( 'result', {} );
293 $runner->insert( 'result.event', 'SUCCESS' );
295 $runner->insert('environment.isRenewal', 1) if $__isrenewal;
296 $runner->insert('environment.isNonCat', 1) if $ctx->{noncat};
297 $runner->insert('environment.nonCatType', $ctx->{noncat_type}) if $ctx->{noncat};
299 if(ref($ctx->{patron_circ_summary})) {
300 $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
301 $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
304 $ctx->{runner} = $runner;
309 sub _add_script_runner_methods {
312 my $runner = $ctx->{runner};
316 # allows a script to fetch a hold that is currently targeting the
318 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
320 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
321 $hold = undef unless $hold;
322 $runner->insert( $key, $hold, 1 );
328 # ------------------------------------------------------------------------------
330 __PACKAGE__->register_method(
331 method => "permit_circ",
332 api_name => "open-ils.circ.checkout.permit",
334 Determines if the given checkout can occur
335 @param authtoken The login session key
336 @param params A trailing hash of named params including
337 barcode : The copy barcode,
338 patron : The patron the checkout is occurring for,
339 renew : true or false - whether or not this is a renewal
340 @return The event that occurred during the permit check.
344 my( $self, $client, $authtoken, $params ) = @_;
347 my ( $requestor, $patron, $ctx, $evt, $circ );
349 # check permisson of the requestor
350 ( $requestor, $patron, $evt ) =
351 $U->checkses_requestor(
352 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
355 # fetch and build the circulation environment
356 if( !( $ctx = $params->{_ctx}) ) {
358 ( $ctx, $evt ) = create_circ_ctx( %$params,
360 requestor => $requestor,
362 fetch_patron_circ_summary => 1,
363 fetch_copy_statuses => 1,
364 fetch_copy_locations => 1,
369 ($circ, $evt) = $U->fetch_open_circ($ctx->{copy}->id)
370 if ( !$__isrenewal and $ctx->{copy});
372 return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $evt;
374 return _run_permit_scripts($ctx);
379 # Runs the patron and copy permit scripts
380 # if this is a non-cat circulation, the copy permit script
382 sub _run_permit_scripts {
384 my $runner = $ctx->{runner};
385 my $patronid = $ctx->{patron}->id;
386 my $barcode = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
389 $runner->load($scripts{circ_permit_patron});
390 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
391 my $evtname = $runner->retrieve('result.event');
392 $logger->activity("circ_permit_patron for user $patronid returned event: $evtname");
394 return OpenILS::Event->new($evtname) if $evtname ne 'SUCCESS';
396 my $key = _cache_permit_key();
398 if( $ctx->{noncat} ) {
399 $logger->debug("Exiting circ permit early because item is a non-cataloged item");
400 return OpenILS::Event->new('SUCCESS', payload => $key);
404 $logger->debug("Exiting circ permit early because copy is pre-cataloged");
405 return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
408 $runner->load($scripts{circ_permit_copy});
409 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
410 $evtname = $runner->retrieve('result.event');
411 $logger->activity("circ_permit_copy for user $patronid ".
412 "and copy $barcode returned event: $evtname");
414 return OpenILS::Event->new($evtname, payload => $key) if( $evtname eq 'SUCCESS' );
415 return OpenILS::Event->new($evtname);
418 # takes copyid, patronid, and requestor id
419 sub _cache_permit_key {
420 my $key = md5_hex( time() . rand() . "$$" );
421 $logger->debug("Setting circ permit key to $key");
422 $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
426 sub _check_permit_key {
428 $logger->debug("Fetching circ permit key $key");
429 my $k = "oils_permit_key_$key";
430 my $one = $cache_handle->get_cache($k);
431 $cache_handle->delete_cache($k);
432 return ($one) ? 1 : 0;
436 # ------------------------------------------------------------------------------
438 __PACKAGE__->register_method(
439 method => "checkout",
440 api_name => "open-ils.circ.checkout",
443 @param authtoken The login session key
444 @param params A named hash of params including:
446 barcode If no copy is provided, the copy is retrieved via barcode
447 copyid If no copy or barcode is provide, the copy id will be use
448 patron The patron's id
449 noncat True if this is a circulation for a non-cataloted item
450 noncat_type The non-cataloged type id
451 noncat_circ_lib The location for the noncat circ.
452 precat The item has yet to be cataloged
453 dummy_title The temporary title of the pre-cataloded item
454 dummy_author The temporary authr of the pre-cataloded item
455 Default is the home org of the staff member
456 @return The SUCCESS event on success, any other event depending on the error
460 my( $self, $client, $authtoken, $params ) = @_;
463 my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
464 my $key = $params->{permit_key};
466 # if this is a renewal, then the requestor does not have to
467 # have checkout privelages
468 ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
469 ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKOUT' ) unless $__isrenewal;
471 $logger->debug("REQUESTOR event: " . ref($requestor));
474 ( $patron, $evt ) = $U->fetch_user($params->{patron});
478 # set the circ lib to the home org of the requestor if not specified
479 my $circlib = (defined($params->{circ_lib})) ?
480 $params->{circ_lib} : $requestor->home_ou;
482 # if this is a non-cataloged item, check it out and return
483 return _checkout_noncat(
484 $key, $requestor, $patron, %$params ) if $params->{noncat};
486 # if this item has yet to be cataloged, make sure a dummy copy exists
487 ( $params->{copy}, $evt ) = _make_precat_copy(
488 $requestor, $circlib, $params ) if $params->{precat};
491 my $session = $U->start_db_session();
493 # fetch and build the circulation environment
494 if( !( $ctx = $params->{_ctx}) ) {
495 ( $ctx, $evt ) = create_circ_ctx( %$params,
497 requestor => $requestor,
500 fetch_patron_circ_summary => 1,
501 fetch_copy_statuses => 1,
502 fetch_copy_locations => 1,
506 $ctx->{session} = $session;
508 my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
509 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')
510 unless _check_permit_key($key);
512 $ctx->{circ_lib} = $circlib;
514 $evt = _run_checkout_scripts($ctx);
517 _build_checkout_circ_object($ctx);
519 $evt = _commit_checkout_circ_object($ctx);
522 _update_checkout_copy($ctx);
524 $evt = _handle_related_holds($ctx);
528 $U->commit_db_session($session);
529 my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
531 return OpenILS::Event->new('SUCCESS',
533 copy => $ctx->{copy},
534 circ => $ctx->{circ},
540 sub _make_precat_copy {
541 my ( $requestor, $circlib, $params ) = @_;
543 my( $copy, undef ) = _find_copy_by_attr(%$params);
546 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
547 return ($copy, undef);
550 $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
552 my $evt = OpenILS::Event->new(
553 'BAD_PARAMS', desc => "Dummy title or author not provided" )
554 unless ( $params->{dummy_title} and $params->{dummy_author} );
555 return (undef, $evt) if $evt;
557 $copy = Fieldmapper::asset::copy->new;
558 $copy->circ_lib($circlib);
559 $copy->creator($requestor->id);
560 $copy->editor($requestor->id);
561 $copy->barcode($params->{barcode});
562 $copy->call_number(-1); #special CN for precat materials
563 $copy->loan_duration(&PRECAT_LOAN_DURATION); # these two should come from constants
564 $copy->fine_level(&PRECAT_FINE_LEVEL);
566 $copy->dummy_title($params->{dummy_title});
567 $copy->dummy_author($params->{dummy_author});
569 my $id = $U->storagereq(
570 'open-ils.storage.direct.asset.copy.create', $copy );
571 return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
573 $logger->debug("Pre-cataloged copy successfully created");
574 return $U->fetch_copy($id);
578 sub _run_checkout_scripts {
584 my $runner = $ctx->{runner};
586 $runner->insert('result.durationLevel');
587 $runner->insert('result.durationRule');
588 $runner->insert('result.recurringFinesRule');
589 $runner->insert('result.recurringFinesLevel');
590 $runner->insert('result.maxFine');
592 $runner->load($scripts{circ_duration});
593 $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
594 my $duration = $runner->retrieve('result.durationRule');
595 $logger->debug("Circ duration script yielded a duration rule of: $duration");
597 $runner->load($scripts{circ_recurring_fines});
598 $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
599 my $recurring = $runner->retrieve('result.recurringFinesRule');
600 $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
602 $runner->load($scripts{circ_max_fines});
603 $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
604 my $max_fine = $runner->retrieve('result.maxFine');
605 $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
607 ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
609 ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
611 ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
614 $ctx->{duration_level} = $runner->retrieve('result.durationLevel');
615 $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
616 $ctx->{duration_rule} = $duration;
617 $ctx->{recurring_fines_rule} = $recurring;
618 $ctx->{max_fine_rule} = $max_fine;
623 sub _build_checkout_circ_object {
627 my $circ = new Fieldmapper::action::circulation;
628 my $duration = $ctx->{duration_rule};
629 my $max = $ctx->{max_fine_rule};
630 my $recurring = $ctx->{recurring_fines_rule};
631 my $copy = $ctx->{copy};
632 my $patron = $ctx->{patron};
633 my $dur_level = $ctx->{duration_level};
634 my $rec_level = $ctx->{recurring_fines_level};
636 $circ->duration( $duration->shrt ) if ($dur_level == 1);
637 $circ->duration( $duration->normal ) if ($dur_level == 2);
638 $circ->duration( $duration->extended ) if ($dur_level == 3);
640 $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
641 $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
642 $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
644 $circ->duration_rule( $duration->name );
645 $circ->recuring_fine_rule( $recurring->name );
646 $circ->max_fine_rule( $max->name );
647 $circ->max_fine( $max->amount );
649 $circ->fine_interval($recurring->recurance_interval);
650 $circ->renewal_remaining( $duration->max_renewals );
651 $circ->target_copy( $copy->id );
652 $circ->usr( $patron->id );
653 $circ->circ_lib( $ctx->{circ_lib} );
656 $logger->debug("Circ is a renewal. Setting renewal_remaining to " . $ctx->{renewal_remaining} );
657 $circ->opac_renewal(1);
658 $circ->renewal_remaining($ctx->{renewal_remaining});
659 $circ->circ_staff($ctx->{requestor}->id);
662 # if a patron is renewing, 'requestor' will be the patron
663 $circ->circ_staff( $ctx->{requestor}->id );
664 _set_circ_due_date($circ);
665 $ctx->{circ} = $circ;
668 sub _create_due_date {
669 my $duration = shift;
672 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
673 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
675 $year += 1900; $mon += 1;
676 my $due_date = sprintf(
677 '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
678 $year, $mon, $mday, $hour, $min, $sec);
682 sub _set_circ_due_date {
685 my $dd = _create_due_date($circ->duration);
686 $logger->debug("Checkout setting due date on circ to: $dd");
687 $circ->due_date($dd);
690 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
691 sub _update_checkout_copy {
694 my $copy = $ctx->{copy};
697 for my $status (@{$cache{copy_statuses}}) { #XXX Abstractify me
698 $s = $status if( $status->name eq 'Checked out' );
701 $copy->status( $s->id ) if $s;
702 $copy->editor( $ctx->{requestor}->id );
703 $copy->edit_date( 'now' );
704 $copy->location( $copy->location->id );
705 $copy->circ_lib( $copy->circ_lib->id );
707 $logger->debug("Updating editor info on copy in checkout: " . $copy->id );
708 $ctx->{session}->request(
709 'open-ils.storage.direct.asset.copy.update', $copy )->gather(1);
712 # commits the circ object to the db then fleshes the circ with rules objects
713 sub _commit_checkout_circ_object {
716 my $circ = $ctx->{circ};
720 my $r = $ctx->{session}->request(
721 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
723 return $U->DB_UPDATE_FAILED($circ) unless $r;
725 $logger->debug("Created a new circ object in checkout: $r");
728 $circ->duration_rule($ctx->{duration_rule});
729 $circ->max_fine_rule($ctx->{max_fine_rule});
730 $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
736 # sees if there are any holds that this copy
737 sub _handle_related_holds {
740 my $copy = $ctx->{copy};
741 my $patron = $ctx->{patron};
742 my $holds = $holdcode->fetch_related_holds($copy->id);
745 if(ref($holds) && @$holds) {
747 # for now, just sort by id to get what should be the oldest hold
748 $holds = [ sort { $a->id <=> $b->id } @$holds ];
749 $holds = [ grep { $_->usr eq $patron->id } @$holds ];
752 my $hold = $holds->[0];
754 $logger->debug("Related hold found in checkout: " . $hold->id );
756 $hold->fulfillment_time('now');
757 my $r = $ctx->{session}->request(
758 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
759 return $U->DB_UPDATE_FAILED( $hold ) unless $r;
767 sub _checkout_noncat {
768 my ( $key, $requestor, $patron, %params ) = @_;
769 my( $circ, $circlib, $evt );
772 $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
774 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')
775 unless _check_permit_key($key);
777 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
778 $requestor->id, $patron->id, $circlib, $params{noncat_type} );
781 return OpenILS::Event->new(
782 'SUCCESS', payload => { noncat_circ => $circ } );
786 # ------------------------------------------------------------------------------
788 __PACKAGE__->register_method(
790 api_name => "open-ils.circ.checkin",
791 notes => <<" NOTES");
792 PARAMS( authtoken, barcode => bc )
793 Checks in based on barcode
794 Returns an event object whose payload contains the record, circ, and copy
795 If the item needs to be routed, the event is a ROUTE_COPY event
796 with an additional 'route_to' variable set on the event
800 my( $self, $client, $authtoken, $params ) = @_;
803 my( $ctx, $requestor, $evt, $patron, $circ );
805 ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
806 ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKIN' ) unless $__isrenewal;
808 ( $patron, $evt ) = $U->fetch_user($params->{patron});
811 if( !( $ctx = $params->{_ctx}) ) {
812 ( $ctx, $evt ) = create_circ_ctx( %$params,
814 requestor => $requestor,
815 #session => $session,
817 fetch_patron_circ_summary => 1,
818 fetch_copy_statuses => 1,
819 fetch_copy_locations => 1,
825 return OpenILS::Event->new('COPY_NOT_FOUND') unless $ctx->{copy};
826 ( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
829 # XXX Use the old checkin for now XXX
830 my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
831 my ($status) = $checkin->run($authtoken, $ctx->{copy}->barcode, 1);
833 return OpenILS::Event->new('PERM_FAILURE')
834 if ref($status) eq "Fieldmapper::perm_ex";
835 return OpenILS::Event->new('UNKNOWN',
836 payload => $status ) if ($status->{status} ne "0");
837 return OpenILS::Event->new('SUCCESS');
840 # ------------------------------------------------------------------------------
842 __PACKAGE__->register_method(
844 api_name => "open-ils.circ.renew_",
845 notes => <<" NOTES");
846 PARAMS( authtoken, circ => circ_id );
847 open-ils.circ.renew(login_session, circ_object);
848 Renews the provided circulation. login_session is the requestor of the
849 renewal and if the logged in user is not the same as circ->usr, then
850 the logged in user must have RENEW_CIRC permissions.
854 my( $self, $client, $authtoken, $params ) = @_;
857 my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
860 # if requesting a renewal for someone else, you must have
862 ( $requestor, $patron, $evt ) = $U->checkses_requestor(
863 $authtoken, $params->{patron}, 'RENEW_CIRC' );
866 # fetch and build the circulation environment
867 ( $ctx, $evt ) = create_circ_ctx( %$params,
869 requestor => $requestor,
871 fetch_patron_circ_summary => 1,
872 fetch_copy_statuses => 1,
873 fetch_copy_locations => 1,
876 $params->{_ctx} = $ctx;
878 ($circ, $evt) = _check_renewals($ctx);
880 $ctx->{old_circ} = $circ;
881 $ctx->{renewal_remaining} = $circ->renewal_remaining - 1;
882 return $evt if( ($evt = _run_renew_scripts($ctx)) );
884 $params->{permit_key} = $evt->{payload};
885 $evt = $self->checkin($client, $authtoken, $params );
886 return $evt unless $U->event_equals($evt, 'SUCCESS');
888 ($circ->{copy}, undef) = $U->fetch_copy($circ->{copy}->id);
889 _doctor_copy_object($ctx); #re-flesh the copy since it's been checked in
891 $evt = $self->permit_circ($client, $authtoken, $params );
892 if( $U->event_equals($evt, 'ITEM_NOT_CATALOGED')) {
895 return $evt unless $U->event_equals($evt, 'SUCCESS');
899 $evt = $self->checkout($client, $authtoken, $params );
905 sub _check_renewals {
908 my( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
909 return (undef, $evt) if $evt;
910 $evt = OpenILS::Event->new(
911 'MAX_RENEWALS_REACHED') if $circ->renewal_remaining < 1;
912 return ($circ, $evt);
915 sub _run_renew_scripts {
917 my $runner = $ctx->{runner};
920 $runner->load($scripts{circ_permit_renew});
921 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
922 my $evtname = $runner->retrieve('result.event');
923 $logger->activity("circ_permit_renew for user ".$ctx->{patron}." returned event: $evtname");
925 return OpenILS::Event->new($evtname) if $evtname ne 'SUCCESS';