1 # ---------------------------------------------------------------
2 # Copyright (C) 2005 Georgia Public Library Service
3 # Bill Erickson <highfalutin@gmail.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
16 package OpenILS::Application::Circ::Rules;
17 use base qw/OpenSRF::Application/;
18 use strict; use warnings;
20 use OpenSRF::Utils::SettingsClient;
21 use OpenILS::Utils::Fieldmapper;
23 use OpenSRF::Utils::Logger qw(:level);
25 use Template qw(:template);
28 use Time::HiRes qw(time);
29 use OpenILS::Utils::ModsParser;
32 use OpenSRF::EX qw(:try);
34 use OpenILS::Application::AppUtils;
35 my $apputils = "OpenILS::Application::AppUtils";
36 use Digest::MD5 qw(md5_hex);
38 my $log = "OpenSRF::Utils::Logger";
40 # ----------------------------------------------------------------
43 my $permission_script;
45 my $recurring_fines_script;
47 my $permit_hold_script;
48 my $permit_renew_script;
49 # ----------------------------------------------------------------
52 # data used for this circulation transaction
53 my $circ_objects = {};
55 # some static data from the database
59 my $shelving_locations;
64 # memcache for caching the circ objects
68 use constant NO_COPY => 100;
73 my $conf = OpenSRF::Utils::SettingsClient->new;
75 # ----------------------------------------------------------------
76 # set up the rules scripts
77 # ----------------------------------------------------------------
78 $circ_script = $conf->config_value(
79 "apps", "open-ils.circ","app_settings", "rules", "main");
81 $permission_script = $conf->config_value(
82 "apps", "open-ils.circ","app_settings", "rules", "permission");
84 $duration_script = $conf->config_value(
85 "apps", "open-ils.circ","app_settings", "rules", "duration");
87 $recurring_fines_script = $conf->config_value(
88 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
90 $max_fines_script = $conf->config_value(
91 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
93 $permit_hold_script = $conf->config_value(
94 "apps", "open-ils.circ","app_settings", "rules", "permit_hold");
96 $permit_renew_script = $conf->config_value(
97 "apps", "open-ils.circ","app_settings", "rules", "permit_renew");
99 $log->debug("Loaded rules scripts for circ:\n".
100 "main - $circ_script : permit circ - $permission_script\n".
101 "duration - $duration_script : recurring - $recurring_fines_script\n".
102 "max fines - $max_fines_script : permit hold - $permit_hold_script", DEBUG);
105 $cache_handle = OpenSRF::Utils::Cache->new();
109 sub _grab_patron_standings {
111 if(!$patron_standings) {
112 my $standing_req = $session->request(
113 "open-ils.storage.direct.config.standing.retrieve.all.atomic");
114 $patron_standings = $standing_req->gather(1);
116 { map { (''.$_->id => $_->value) } @$patron_standings };
120 sub _grab_patron_profiles {
122 if(!$patron_profiles) {
123 my $profile_req = $session->request(
124 "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
125 $patron_profiles = $profile_req->gather(1);
127 { map { (''.$_->id => $_->name) } @$patron_profiles };
134 my $patron_id = shift;
135 my $patron_req = $session->request(
136 "open-ils.storage.direct.actor.user.retrieve",
138 return $patron_req->gather(1);
142 sub _grab_title_by_copy {
145 my $title_req = $session->request(
146 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
148 return $title_req->gather(1);
151 sub _grab_patron_summary {
153 my $patron_id = shift;
154 my $summary_req = $session->request(
155 "open-ils.storage.action.circulation.patron_summary",
157 return $summary_req->gather(1);
160 sub _grab_copy_by_barcode {
161 my($session, $barcode) = @_;
162 warn "Searching for copy with barcode $barcode\n";
163 my $copy_req = $session->request(
164 "open-ils.storage.fleshed.asset.copy.search.barcode",
166 return $copy_req->gather(1);
169 sub _grab_copy_by_id {
170 my($session, $id) = @_;
171 warn "Searching for copy with id $id\n";
172 my $copy_req = $session->request(
173 "open-ils.storage.direct.asset.copy.retrieve",
175 my $c = $copy_req->gather(1);
176 if($c) { return _grab_copy_by_barcode($session, $c->barcode); }
181 sub gather_hold_objects {
182 my($session, $hold, $copy, $args) = @_;
184 _grab_patron_standings($session);
185 _grab_patron_profiles($session);
189 $copy = _grab_copy_by_barcode($session, $copy->barcode);
191 my $hold_objects = {};
192 $hold_objects->{standings} = $patron_standings;
193 $hold_objects->{copy} = $copy;
194 $hold_objects->{hold} = $hold;
195 $hold_objects->{title} = $$args{title} || _grab_title_by_copy($session, $copy->id);
196 $hold_objects->{requestor} = _grab_user($session, $hold->requestor);
197 my $patron = _grab_user($session, $hold->usr);
199 $copy->status( $copy->status->name );
200 $patron->standing($patron_standings->{$patron->standing()});
201 $patron->profile( $patron_profiles->{$patron->profile});
203 $hold_objects->{patron} = $patron;
205 return $hold_objects;
210 __PACKAGE__->register_method(
211 method => "permit_hold",
212 api_name => "open-ils.circ.permit_hold",
213 notes => <<" NOTES");
214 Determines whether a given copy is eligible to be held
218 my( $self, $client, $hold, $copy, $args ) = @_;
220 my $session = OpenSRF::AppSession->create("open-ils.storage");
222 # collect items necessary for circ calculation
223 my $hold_objects = gather_hold_objects( $session, $hold, $copy, $args );
225 $stash = Template::Stash->new(
226 circ_objects => $hold_objects,
229 $stash->set("run_block", $permit_hold_script);
231 # grab the number of copies checked out by the patron as
232 # well as the total fines
233 my $summary = _grab_patron_summary($session, $hold_objects->{patron}->id);
235 $summary->[1] ||= 0.0;
237 $stash->set("patron_copies", $summary->[0] );
238 $stash->set("patron_fines", $summary->[1] );
240 # run the permissibility script
242 my $result = $stash->get("result");
244 # 0 means OK in the script
245 return 1 if($result->[0] == 0);
254 # ----------------------------------------------------------------
255 # Collect all of the objects necessary for calculating the
257 # ----------------------------------------------------------------
258 sub gather_circ_objects {
259 my( $session, $barcode_string, $patron_id, $copy ) = @_;
261 throw OpenSRF::EX::ERROR
262 ("gather_circ_objects needs data")
263 unless ($barcode_string and $patron_id);
265 warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
267 # see if all of the circ objects are in cache
268 # my $cache_key = "circ_object_" . md5_hex( $barcode_string, $patron_id );
269 # $circ_objects = $cache_handle->get_cache($cache_key);
271 # if($circ_objects) {
272 # $stash = Template::Stash->new(
273 # circ_objects => $circ_objects,
275 # target_copy_status => 1,
280 # only necessary if the circ objects have not been built yet
282 _grab_patron_standings($session);
283 _grab_patron_profiles($session);
287 $copy = _grab_copy_by_barcode($session, $barcode_string);
288 if(!$copy) { return NO_COPY; }
291 my $patron = _grab_user($session, $patron_id);
293 $copy->status( $copy->status->name );
294 $circ_objects->{copy} = $copy;
296 $patron->standing($patron_standings->{$patron->standing()});
297 $patron->profile( $patron_profiles->{$patron->profile});
298 $circ_objects->{patron} = $patron;
299 $circ_objects->{standings} = $patron_standings;
301 #$circ_objects->{title} = $title_req->gather(1);
302 $circ_objects->{title} = _grab_title_by_copy($session, $circ_objects->{copy}->id);
303 # $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
305 $stash = Template::Stash->new(
306 circ_objects => $circ_objects,
308 target_copy_status => 1,
318 my $template = Template->new(
326 my $status = $template->process($circ_script);
329 throw OpenSRF::EX::ERROR
330 ("Error processing circ script " . $template->error());
333 warn "Script result: $result\n";
339 __PACKAGE__->register_method(
340 method => "permit_circ",
341 api_name => "open-ils.circ.permit_checkout",
345 my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
347 my $copy_status_mangled;
351 if(defined($outstanding_count) && $outstanding_count eq "renew") {
353 $outstanding_count = 0;
354 } else { $outstanding_count ||= 0; }
356 my $session = OpenSRF::AppSession->create("open-ils.storage");
358 # collect items necessary for circ calculation
359 my $status = gather_circ_objects( $session, $barcode, $user_id );
361 if( $status == NO_COPY ) {
362 return { record => undef,
364 text => "No copy available with barcode $barcode"
368 my $copy = $stash->get("circ_objects")->{copy};
370 warn "Found copy in permit: " . $copy->id . "\n";
372 warn "Copy status in checkout is " . $stash->get("circ_objects")->{copy}->status . "\n";
374 $stash->set("run_block", $permission_script);
376 # grab the number of copies checked out by the patron as
377 # well as the total fines
378 my $summary_req = $session->request(
379 "open-ils.storage.action.circulation.patron_summary",
380 $stash->get("circ_objects")->{patron}->id );
382 my $summary = $summary_req->gather(1);
385 $summary->[1] ||= 0.0;
387 $stash->set("patron_copies", $summary->[0] + $outstanding_count );
388 $stash->set("patron_fines", $summary->[1] );
389 $stash->set("renew", 1) if $renew;
391 # run the permissibility script
394 my $arr = $stash->get("result");
396 if( $arr->[0] eq "0" ) { # and $copy_status_mangled == 8) {
398 # see if this copy is needed to fulfil a hold
400 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
402 my $hold = $session->request(
403 "open-ils.storage.direct.action.hold_request.search.atomic",
404 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
408 if($hold) { warn "Found hold in circ permit with id " . $hold->id . "\n"; }
412 warn "Found unfulfilled hold in permit ". $hold->id . "\n";
414 if( $hold->usr eq $user_id ) {
415 return { status => 0, text => "OK" };
418 return { status => 6,
419 text => "Copy is needed by a different user to fulfill a hold" };
424 return { status => $arr->[0], text => $arr->[1] };
430 __PACKAGE__->register_method(
431 method => "circulate",
432 api_name => "open-ils.circ.checkout.barcode",
436 my( $self, $client, $user_session, $barcode, $patron, $isrenew, $numrenews ) = @_;
439 my $user = $apputils->check_user_session($user_session);
440 my $session = $apputils->start_db_session();
442 my $copy = _grab_copy_by_barcode($session, $barcode);
443 my $realstatus = $copy->status->id;
445 warn "Checkout copy status is $realstatus\n";
447 gather_circ_objects( $session, $barcode, $patron, $copy );
449 # grab the copy statuses if we don't already have them
450 if(!$copy_statuses) {
451 my $csreq = $session->request(
452 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
453 $copy_statuses = $csreq->gather(1);
456 # put copy statuses into the stash
457 $stash->set("copy_statuses", $copy_statuses );
459 $copy = $circ_objects->{copy};
460 my ($circ, $duration, $recurring, $max) = run_circ_scripts($session);
463 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
464 gmtime(OpenSRF::Utils->interval_to_seconds($circ->duration) + int(time()));
465 $year += 1900; $mon += 1;
466 my $due_date = sprintf(
467 '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
468 $year, $mon, $mday, $hour, $min, $sec);
470 warn "Setting due date to $due_date\n";
471 $circ->due_date($due_date);
472 $circ->circ_lib($user->home_ou);
475 warn "Renewing circ.... ".$circ->id ." and setting num renews to " . $numrenews - 1 . "\n";
478 $circ->renewal_remaining($numrenews - 1);
482 # commit new circ object to db
483 my $commit = $session->request(
484 "open-ils.storage.direct.action.circulation.create", $circ );
485 my $id = $commit->gather(1);
488 throw OpenSRF::EX::ERROR
489 ("Error creating new circulation object");
492 # update the copy with the new circ
493 $copy->status( $stash->get("target_copy_status") );
494 $copy->location( $copy->location->id );
495 $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
498 my $copy_update = $session->request(
499 "open-ils.storage.direct.asset.copy.update",
501 $copy_update->gather(1);
504 if( $realstatus eq "8" ) { # on holds shelf
507 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
509 my $hold = $session->request(
510 "open-ils.storage.direct.action.hold_request.search.atomic",
511 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
516 $hold->fulfillment_time("now");
517 my $s = $session->request(
518 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
519 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
523 $apputils->commit_db_session($session);
525 # remove our circ object from the cache
526 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
528 # re-retrieve the the committed circ object
529 $circ = $apputils->simple_scalar_request(
531 "open-ils.storage.direct.action.circulation.retrieve",
535 # push the rules and due date into the circ object
536 $circ->duration_rule($duration);
537 $circ->max_fine_rule($max);
538 $circ->recuring_fine_rule($recurring);
540 # turn the biblio record into a friendly object
541 my $obj = $stash->get("circ_objects");
542 my $u = OpenILS::Utils::ModsParser->new();
543 $u->start_mods_batch( $circ_objects->{title}->marc() );
544 my $mods = $u->finish_mods_batch();
547 return { circ => $circ, copy => $copy, record => $mods };
553 # runs the duration, recurring_fines, and max_fines scripts.
554 # builds the new circ object based on the rules returned from
556 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
557 sub run_circ_scripts {
560 # go through all of the scripts and process
561 # each script returns
562 # [ rule_name, level (appropriate to the script) ]
563 $stash->set("result", [] );
564 $stash->set("run_block", $duration_script);
566 my $duration_rule = $stash->get("result");
568 $stash->set("run_block", $recurring_fines_script);
569 $stash->set("result", [] );
571 my $rec_fines_rule = $stash->get("result");
573 $stash->set("run_block", $max_fines_script);
574 $stash->set("result", [] );
576 my $max_fines_rule = $stash->get("result");
578 my $obj = $stash->get("circ_objects");
580 # ----------------------------------------------------------
581 # find the rules objects based on the rule names returned from
582 # the various scripts.
583 my $dur_req = $session->request(
584 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
585 $duration_rule->[0] );
587 my $rec_req = $session->request(
588 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
589 $rec_fines_rule->[0] );
591 my $max_req = $session->request(
592 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
593 $max_fines_rule->[0] );
595 my $duration = $dur_req->gather(1)->[0];
596 my $recurring = $rec_req->gather(1)->[0];
597 my $max = $max_req->gather(1)->[0];
599 my $copy = $circ_objects->{copy};
602 warn "Building a new circulation object with\n".
603 "=> copy " . Dumper($copy) .
604 "=> duration_rule " . Dumper($duration_rule) .
605 "=> rec_files_rule " . Dumper($rec_fines_rule) .
606 "=> duration " . Dumper($duration) .
607 "=> recurring " . Dumper($recurring) .
608 "=> max " . Dumper($max);
611 # build the new circ object
612 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
613 $rec_fines_rule->[1], $duration, $recurring, $max );
615 return ($circ, $duration, $recurring, $max);
619 # ------------------------------------------------------------------
620 # Builds a new circ object
621 # ------------------------------------------------------------------
622 sub build_circ_object {
623 my( $session, $copy, $dur_level, $rec_level,
624 $duration, $recurring, $max ) = @_;
626 my $circ = new Fieldmapper::action::circulation;
628 $circ->circ_lib( $copy->circ_lib->id() );
629 if($dur_level == 1) {
630 $circ->duration( $duration->shrt );
631 } elsif($dur_level == 2) {
632 $circ->duration( $duration->normal );
633 } elsif($dur_level == 3) {
634 $circ->duration( $duration->extended );
637 if($rec_level == 1) {
638 $circ->recuring_fine( $recurring->low );
639 } elsif($rec_level == 2) {
640 $circ->recuring_fine( $recurring->normal );
641 } elsif($rec_level == 3) {
642 $circ->recuring_fine( $recurring->high );
645 $circ->duration_rule( $duration->name );
646 $circ->recuring_fine_rule( $recurring->name );
647 $circ->max_fine_rule( $max->name );
648 $circ->max_fine( $max->amount );
650 $circ->fine_interval($recurring->recurance_interval);
651 $circ->renewal_remaining( $duration->max_renewals );
652 $circ->target_copy( $copy->id );
653 $circ->usr( $circ_objects->{patron}->id );
659 __PACKAGE__->register_method(
660 method => "transit_receive",
661 api_name => "open-ils.circ.transit.receive",
662 notes => <<" NOTES");
663 Receives a copy that is in transit.
664 Params are login_session and copyid.
665 Logged in user must have COPY_CHECKIN priveleges.
667 status 3 means that this transit is destined for somewhere else
668 status 10 means the copy is not in transit
669 status 11 means the transit is complete, does not need processing
670 status 12 means copy is in transit but no tansit was found
674 sub transit_receive {
675 my( $self, $client, $login_session, $copyid ) = @_;
677 my $user = $apputils->check_user_session($login_session);
679 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
680 return OpenILS::Perm->new("COPY_CHECKIN");
683 warn "Receiving copy for transit $copyid\n";
685 my $session = $apputils->start_db_session();
686 my $copy = $session->request(
687 "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
690 if(!$copy->status eq "6") {
691 return { status => 10, route_to => $copy->circ_lib };
695 $transit = $session->request(
696 "open-ils.storage.direct.action.transit_copy.search_where",
697 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
701 warn "Found transit for copy $copyid\n";
703 if( defined($transit->dest_recv_time) ) {
704 return { status => 11, route_to => $copy->circ_lib,
705 text => "Transit is already complete for this copy" };
708 if($transit->dest ne $user->home_ou) {
709 return { status => 3, route_to => $transit->dest,
710 text => "Copy is destined for a different location" };
713 $transit->dest_recv_time("now");
714 my $s = $session->request(
715 "open-ils.storage.direct.action.transit_copy.update", $transit )->gather(1);
717 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating transit " . $transit->id . "\n"); }
719 my $holdtransit = $session->request(
720 "open-ils.storage.direct.action.hold_transit_copy.retrieve",
721 $transit->id )->gather(1);
725 warn "Found hold transit for copy $copyid\n";
727 my $hold = $session->request(
728 "open-ils.storage.direct.action.hold_request.retrieve",
729 $holdtransit->hold )->gather(1);
732 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
735 # put copy on the holds shelf
736 $copy->status(8); #hold shelf status
737 $copy->editor($user->id); #hold shelf status
738 $copy->edit_date("now"); #hold shelf status
740 warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
742 my $s = $session->request(
743 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
744 if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
746 $apputils->commit_db_session($session);
748 return { status => 0, route_to => $hold->pickup_lib, text => "Transit Complete" };
752 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
759 __PACKAGE__->register_method(
761 api_name => "open-ils.circ.checkin.barcode",
762 notes => <<" NOTES");
763 Checks in based on barcode
764 Returns record, status, text, circ, copy, route_to
767 1 = 'copy required to fulfil a hold'
771 my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
778 my $user = $apputils->check_user_session($user_session);
780 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
781 return OpenILS::Perm->new("COPY_CHECKIN");
784 my $session = $apputils->start_db_session();
790 warn "retrieving copy for checkin\n";
793 my $copy_req = $session->request(
794 "open-ils.storage.direct.asset.copy.search.barcode.atomic",
796 $copy = $copy_req->gather(1)->[0];
798 $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
801 if($copy->status eq "6") { #copy is in transit, deal with it
802 my $method = $self->method_lookup("open-ils.circ.transit.receive");
803 ($transit_return) = $method->run( $user_session, $copy->id );
808 if(!$shelving_locations) {
809 my $sh_req = $session->request(
810 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
811 $shelving_locations = $sh_req->gather(1);
812 $shelving_locations =
813 { map { (''.$_->id => $_->name) } @$shelving_locations };
819 # find circ's where the transaction is still open for the
820 # given copy. should only be one.
821 warn "Retrieving circ for checkin\n";
822 my $circ_req = $session->request(
823 "open-ils.storage.direct.action.circulation.search.atomic",
824 { target_copy => $copy->id, xact_finish => undef } );
826 $circ = $circ_req->gather(1)->[0];
830 $err = "No circulation exists for the given barcode";
834 $transaction = $session->request(
835 "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
837 warn "Checking in circ ". $circ->id . "\n";
839 $circ->stop_fines("CHECKIN");
840 $circ->stop_fines("RENEW") if($isrenewal);
841 $circ->xact_finish("now") if($transaction->balance_owed <= 0 );
844 $circ->xact_finish($backdate);
846 # void any bills the resulted after the backdate time
847 my $bills = $session->request(
848 "open-ils.storage.direct.money.billing.search_where.atomic",
849 billing_ts => { ">=" => $backdate })->gather(1);
852 for my $bill (@$bills) {
855 my $s = $session->request(
856 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
859 throw OpenSRF::EX::ERROR
860 ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
866 my $cp_up = $session->request(
867 "open-ils.storage.direct.asset.copy.update", $copy );
870 my $ci_up = $session->request(
871 "open-ils.storage.direct.action.circulation.update",
876 warn "Checkin succeeded\n";
882 $err = "Error checking in: $e";
885 if($transit_return) { return $transit_return; }
889 return { record => undef, status => -1, text => $err };
894 my $status_text = "OK";
896 # see if this copy can fulfill a hold
897 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
899 my $route_to = $shelving_locations->{$copy->location};
903 $status_text = "Copy needed to fulfill hold";
904 $route_to = $hold->pickup_lib;
907 $apputils->commit_db_session($session);
909 my $record = $apputils->simple_scalar_request(
911 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
914 my $u = OpenILS::Utils::ModsParser->new();
915 $u->start_mods_batch( $record->marc() );
916 my $mods = $u->finish_mods_batch();
921 text => $status_text,
924 route_to => $route_to,
936 # ------------------------------------------------------------------------------
938 # ------------------------------------------------------------------------------
941 __PACKAGE__->register_method(
943 api_name => "open-ils.circ.renew",
944 notes => <<" NOTES");
945 open-ils.circ.renew(login_session, circ_object);
946 Renews the provided circulation. login_session is the requestor of the
947 renewal and if the logged in user is not the same as circ->usr, then
948 the logged in user must have RENEW_CIRC permissions.
952 my($self, $client, $login_session, $circ) = @_;
954 throw OpenSRF::EX::InvalidArg
955 ("open-ils.circ.renew no circ") unless defined($circ);
957 my $user = $apputils->check_user_session($login_session);
959 my $session = OpenSRF::AppSession->create("open-ils.storage");
960 my $copy = _grab_copy_by_id($session, $circ->target_copy);
962 my $r = $session->request(
963 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
964 $copy->id )->gather(1);
966 my @holdids = map { $_->hold } @$r;
970 my $holds = $session->request(
971 "open-ils.storage.direct.action.hold_request.search_where",
972 { id => \@holdids, current_copy => undef } )->gather(1);
974 if( $holds and $user->id ne $circ->usr ) {
975 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
976 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
980 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex;
985 $circ = $session->request(
986 "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
990 warn "Attempting to renew circ " . $iid . "\n";
992 if($user->id ne $circ->usr) {
993 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
994 return OpenILS::Perm->new("RENEW_CIRC");
998 if($circ->renewal_remaining <= 0) {
999 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1003 # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1004 # XXX check overdue..?
1006 my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1007 my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1008 return $status if ($status->{status} ne "0");
1009 warn "Renewal checkin completed for $iid\n";
1011 my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1012 ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1013 return $status if($status->{status} ne "0");
1014 warn "Renewal permit checkout completed for $iid\n";
1016 my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1017 ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1018 warn "Renewal checkout completed for $iid\n";