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", $barcode );
165 return $copy_req->gather(1);
168 sub _grab_copy_by_id {
169 my($session, $id) = @_;
170 warn "Searching for copy with id $id\n";
171 my $copy_req = $session->request(
172 "open-ils.storage.direct.asset.copy.retrieve",
174 my $c = $copy_req->gather(1);
175 if($c) { return _grab_copy_by_barcode($session, $c->barcode); }
180 sub gather_hold_objects {
181 my($session, $hold, $copy, $args) = @_;
183 _grab_patron_standings($session);
184 _grab_patron_profiles($session);
188 $copy = _grab_copy_by_barcode($session, $copy->barcode) unless ref($copy->circ_lib);
190 my $hold_objects = {};
191 $hold_objects->{standings} = $patron_standings;
192 $hold_objects->{copy} = $copy;
193 $hold_objects->{hold} = $hold;
194 $hold_objects->{title} = $$args{title} || _grab_title_by_copy($session, $copy->id);
195 $hold_objects->{requestor} = $$args{requestor} || _grab_user($session, $hold->requestor);
196 my $patron = $$args{usr} || _grab_user($session, $hold->usr);
198 $copy->status( $copy->status->name );
199 $patron->standing($patron_standings->{$patron->standing()});
200 $patron->profile( $patron_profiles->{$patron->profile});
202 $hold_objects->{patron} = $patron;
204 return $hold_objects;
209 __PACKAGE__->register_method(
210 method => "permit_hold",
211 api_name => "open-ils.circ.permit_hold",
212 notes => <<" NOTES");
213 Determines whether a given copy is eligible to be held
217 my( $self, $client, $hold, $copy, $args ) = @_;
219 my $session = OpenSRF::AppSession->create("open-ils.storage");
221 # collect items necessary for circ calculation
222 my $hold_objects = gather_hold_objects( $session, $hold, $copy, $args );
224 $stash = Template::Stash->new(
225 circ_objects => $hold_objects,
228 $stash->set("run_block", $permit_hold_script);
230 # grab the number of copies checked out by the patron as
231 # well as the total fines
232 my $summary = _grab_patron_summary($session, $hold_objects->{patron}->id);
234 $summary->[1] ||= 0.0;
236 $stash->set("patron_copies", $summary->[0] );
237 $stash->set("patron_fines", $summary->[1] );
239 # run the permissibility script
241 my $result = $stash->get("result");
243 # 0 means OK in the script
244 return 1 if($result->[0] == 0);
253 # ----------------------------------------------------------------
254 # Collect all of the objects necessary for calculating the
256 # ----------------------------------------------------------------
257 sub gather_circ_objects {
258 my( $session, $barcode_string, $patron_id, $copy ) = @_;
260 throw OpenSRF::EX::ERROR
261 ("gather_circ_objects needs data")
262 unless ($barcode_string and $patron_id);
264 warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
266 # see if all of the circ objects are in cache
267 # my $cache_key = "circ_object_" . md5_hex( $barcode_string, $patron_id );
268 # $circ_objects = $cache_handle->get_cache($cache_key);
270 # if($circ_objects) {
271 # $stash = Template::Stash->new(
272 # circ_objects => $circ_objects,
274 # target_copy_status => 1,
279 # only necessary if the circ objects have not been built yet
281 _grab_patron_standings($session);
282 _grab_patron_profiles($session);
286 $copy = _grab_copy_by_barcode($session, $barcode_string);
287 if(!$copy) { return NO_COPY; }
290 my $patron = _grab_user($session, $patron_id);
292 $copy->status( $copy->status->name );
293 $circ_objects->{copy} = $copy;
295 $patron->standing($patron_standings->{$patron->standing()});
296 $patron->profile( $patron_profiles->{$patron->profile});
297 $circ_objects->{patron} = $patron;
298 $circ_objects->{standings} = $patron_standings;
300 #$circ_objects->{title} = $title_req->gather(1);
301 $circ_objects->{title} = _grab_title_by_copy($session, $circ_objects->{copy}->id);
302 # $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
304 $stash = Template::Stash->new(
305 circ_objects => $circ_objects,
307 target_copy_status => 1,
317 my $template = Template->new(
325 my $status = $template->process($circ_script);
328 throw OpenSRF::EX::ERROR
329 ("Error processing circ script " . $template->error());
332 warn "Script result: $result\n";
338 __PACKAGE__->register_method(
339 method => "permit_circ",
340 api_name => "open-ils.circ.permit_checkout",
344 my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
346 my $copy_status_mangled;
350 if(defined($outstanding_count) && $outstanding_count eq "renew") {
352 $outstanding_count = 0;
353 } else { $outstanding_count ||= 0; }
355 my $session = OpenSRF::AppSession->create("open-ils.storage");
357 # collect items necessary for circ calculation
358 my $status = gather_circ_objects( $session, $barcode, $user_id );
360 if( $status == NO_COPY ) {
361 return { record => undef,
363 text => "No copy available with barcode $barcode"
367 my $copy = $stash->get("circ_objects")->{copy};
369 warn "Found copy in permit: " . $copy->id . "\n";
371 warn "Copy status in checkout is " . $stash->get("circ_objects")->{copy}->status . "\n";
373 $stash->set("run_block", $permission_script);
375 # grab the number of copies checked out by the patron as
376 # well as the total fines
377 my $summary_req = $session->request(
378 "open-ils.storage.action.circulation.patron_summary",
379 $stash->get("circ_objects")->{patron}->id );
381 my $summary = $summary_req->gather(1);
384 $summary->[1] ||= 0.0;
386 $stash->set("patron_copies", $summary->[0] + $outstanding_count );
387 $stash->set("patron_fines", $summary->[1] );
388 $stash->set("renew", 1) if $renew;
390 # run the permissibility script
393 my $arr = $stash->get("result");
395 if( $arr->[0] eq "0" ) { # and $copy_status_mangled == 8) {
397 # see if this copy is needed to fulfil a hold
399 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
401 my $hold = $session->request(
402 "open-ils.storage.direct.action.hold_request.search.atomic",
403 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
407 if($hold) { warn "Found hold in circ permit with id " . $hold->id . "\n"; }
411 warn "Found unfulfilled hold in permit ". $hold->id . "\n";
413 if( $hold->usr eq $user_id ) {
414 return { status => 0, text => "OK" };
417 return { status => 6,
418 text => "Copy is needed by a different user to fulfill a hold" };
423 return { status => $arr->[0], text => $arr->[1] };
429 __PACKAGE__->register_method(
430 method => "circulate",
431 api_name => "open-ils.circ.checkout.barcode",
435 my( $self, $client, $user_session, $barcode, $patron, $isrenew, $numrenews ) = @_;
438 my $user = $apputils->check_user_session($user_session);
439 my $session = $apputils->start_db_session();
441 my $copy = _grab_copy_by_barcode($session, $barcode);
442 my $realstatus = $copy->status->id;
444 warn "Checkout copy status is $realstatus\n";
446 gather_circ_objects( $session, $barcode, $patron, $copy );
448 # grab the copy statuses if we don't already have them
449 if(!$copy_statuses) {
450 my $csreq = $session->request(
451 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
452 $copy_statuses = $csreq->gather(1);
455 # put copy statuses into the stash
456 $stash->set("copy_statuses", $copy_statuses );
458 $copy = $circ_objects->{copy};
459 my ($circ, $duration, $recurring, $max) = run_circ_scripts($session);
462 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
463 gmtime(OpenSRF::Utils->interval_to_seconds($circ->duration) + int(time()));
464 $year += 1900; $mon += 1;
465 my $due_date = sprintf(
466 '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
467 $year, $mon, $mday, $hour, $min, $sec);
469 warn "Setting due date to $due_date\n";
470 $circ->due_date($due_date);
471 $circ->circ_lib($user->home_ou);
474 warn "Renewing circ.... ".$circ->id ." and setting num renews to " . $numrenews - 1 . "\n";
477 $circ->renewal_remaining($numrenews - 1);
481 # commit new circ object to db
482 my $commit = $session->request(
483 "open-ils.storage.direct.action.circulation.create", $circ );
484 my $id = $commit->gather(1);
487 throw OpenSRF::EX::ERROR
488 ("Error creating new circulation object");
491 # update the copy with the new circ
492 $copy->status( $stash->get("target_copy_status") );
493 $copy->location( $copy->location->id );
494 $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
497 my $copy_update = $session->request(
498 "open-ils.storage.direct.asset.copy.update",
500 $copy_update->gather(1);
503 if( $realstatus eq "8" ) { # on holds shelf
506 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
508 my $hold = $session->request(
509 "open-ils.storage.direct.action.hold_request.search.atomic",
510 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
515 $hold->fulfillment_time("now");
516 my $s = $session->request(
517 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
518 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
522 $apputils->commit_db_session($session);
524 # remove our circ object from the cache
525 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
527 # re-retrieve the the committed circ object
528 $circ = $apputils->simple_scalar_request(
530 "open-ils.storage.direct.action.circulation.retrieve",
534 # push the rules and due date into the circ object
535 $circ->duration_rule($duration);
536 $circ->max_fine_rule($max);
537 $circ->recuring_fine_rule($recurring);
539 # turn the biblio record into a friendly object
540 my $obj = $stash->get("circ_objects");
541 my $u = OpenILS::Utils::ModsParser->new();
542 $u->start_mods_batch( $circ_objects->{title}->marc() );
543 my $mods = $u->finish_mods_batch();
546 return { circ => $circ, copy => $copy, record => $mods };
552 # runs the duration, recurring_fines, and max_fines scripts.
553 # builds the new circ object based on the rules returned from
555 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
556 sub run_circ_scripts {
559 # go through all of the scripts and process
560 # each script returns
561 # [ rule_name, level (appropriate to the script) ]
562 $stash->set("result", [] );
563 $stash->set("run_block", $duration_script);
565 my $duration_rule = $stash->get("result");
567 $stash->set("run_block", $recurring_fines_script);
568 $stash->set("result", [] );
570 my $rec_fines_rule = $stash->get("result");
572 $stash->set("run_block", $max_fines_script);
573 $stash->set("result", [] );
575 my $max_fines_rule = $stash->get("result");
577 my $obj = $stash->get("circ_objects");
579 # ----------------------------------------------------------
580 # find the rules objects based on the rule names returned from
581 # the various scripts.
582 my $dur_req = $session->request(
583 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
584 $duration_rule->[0] );
586 my $rec_req = $session->request(
587 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
588 $rec_fines_rule->[0] );
590 my $max_req = $session->request(
591 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
592 $max_fines_rule->[0] );
594 my $duration = $dur_req->gather(1)->[0];
595 my $recurring = $rec_req->gather(1)->[0];
596 my $max = $max_req->gather(1)->[0];
598 my $copy = $circ_objects->{copy};
601 warn "Building a new circulation object with\n".
602 "=> copy " . Dumper($copy) .
603 "=> duration_rule " . Dumper($duration_rule) .
604 "=> rec_files_rule " . Dumper($rec_fines_rule) .
605 "=> duration " . Dumper($duration) .
606 "=> recurring " . Dumper($recurring) .
607 "=> max " . Dumper($max);
610 # build the new circ object
611 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
612 $rec_fines_rule->[1], $duration, $recurring, $max );
614 return ($circ, $duration, $recurring, $max);
618 # ------------------------------------------------------------------
619 # Builds a new circ object
620 # ------------------------------------------------------------------
621 sub build_circ_object {
622 my( $session, $copy, $dur_level, $rec_level,
623 $duration, $recurring, $max ) = @_;
625 my $circ = new Fieldmapper::action::circulation;
627 $circ->circ_lib( $copy->circ_lib->id() );
628 if($dur_level == 1) {
629 $circ->duration( $duration->shrt );
630 } elsif($dur_level == 2) {
631 $circ->duration( $duration->normal );
632 } elsif($dur_level == 3) {
633 $circ->duration( $duration->extended );
636 if($rec_level == 1) {
637 $circ->recuring_fine( $recurring->low );
638 } elsif($rec_level == 2) {
639 $circ->recuring_fine( $recurring->normal );
640 } elsif($rec_level == 3) {
641 $circ->recuring_fine( $recurring->high );
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( $circ_objects->{patron}->id );
658 __PACKAGE__->register_method(
659 method => "transit_receive",
660 api_name => "open-ils.circ.transit.receive",
661 notes => <<" NOTES");
662 Receives a copy that is in transit.
663 Params are login_session and copyid.
664 Logged in user must have COPY_CHECKIN priveleges.
666 status 3 means that this transit is destined for somewhere else
667 status 10 means the copy is not in transit
668 status 11 means the transit is complete, does not need processing
669 status 12 means copy is in transit but no tansit was found
673 sub transit_receive {
674 my( $self, $client, $login_session, $copyid ) = @_;
676 my $user = $apputils->check_user_session($login_session);
678 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
679 return OpenILS::Perm->new("COPY_CHECKIN");
682 warn "Receiving copy for transit $copyid\n";
684 my $session = $apputils->start_db_session();
685 my $copy = $session->request(
686 "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
689 if(!$copy->status eq "6") {
690 return { status => 10, route_to => $copy->circ_lib };
694 $transit = $session->request(
695 "open-ils.storage.direct.action.transit_copy.search_where",
696 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
699 my $record = _grab_title_by_copy($session, $copy->id);
700 my $u = OpenILS::Utils::ModsParser->new();
701 $u->start_mods_batch( $record->marc() );
702 $record = $u->finish_mods_batch();
706 warn "Found transit " . $transit->id . " for copy $copyid\n";
708 if( defined($transit->dest_recv_time) ) {
709 return { status => 11, route_to => $copy->circ_lib,
710 text => "Transit is already complete for this copy" };
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 warn "Searching for hold transit with id " . $transit->id . "\n";
721 my $holdtransit = $session->request(
722 "open-ils.storage.direct.action.hold_transit_copy.retrieve",
723 $transit->id )->gather(1);
727 warn "Found hold transit for copy $copyid\n";
729 my $hold = $session->request(
730 "open-ils.storage.direct.action.hold_request.retrieve",
731 $holdtransit->hold )->gather(1);
734 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
737 # put copy on the holds shelf
738 $copy->status(8); #hold shelf status
739 $copy->editor($user->id); #hold shelf status
740 $copy->edit_date("now"); #hold shelf status
742 warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
744 my $s = $session->request(
745 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
746 if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
748 $apputils->commit_db_session($session);
750 return { status => 4, route_to => "Holds Shelf",
751 text => "Transit Complete", record => $record, copy => $copy };
756 if($transit->dest eq $user->home_ou) {
759 $copy->editor($user->id);
760 $copy->edit_date("now");
762 my $s = $session->request(
763 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
764 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
766 my($status, $status_text) = (0, "Transit Complete");
770 if($transit->copy_status eq "3") { #if copy is lost
772 $status_text = "Copy is marked as LOST";
774 $circ = $session->request(
775 "open-ils.storage.direct.action.circulation.search_where",
776 { target_copy => $copy->id, xact_finish => undef },
777 { order_by => "xact_start desc", limit => 1 } )->gather(1);
782 my $transaction = $session->request(
783 "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
785 $circ->xact_finish("now") if($transaction->balance_owed <= 0);
787 my $s = $session->request(
788 "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
791 throw OpenSRF::EX::ERROR ("Error updating circulation " . $circ->id);
797 $apputils->commit_db_session($session);
801 route_to => $user->home_ou,
802 text => $status_text,
809 $apputils->rollback_db_session($session);
813 copy => $copy, record => $record,
814 status => 3, route_to => $transit->dest,
815 text => "Transit needs to be routed" };
824 $apputils->rollback_db_session($session);
825 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
832 __PACKAGE__->register_method(
834 api_name => "open-ils.circ.checkin.barcode",
835 notes => <<" NOTES");
836 Checks in based on barcode
837 Returns record, status, text, circ, copy, route_to
840 1 = 'copy required to fulfil a hold'
841 2 = "copy is marked as lost"
843 4 = "transit for hold complete, put on holds shelf"
847 my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
855 my $status_text = "OK";
859 my $user = $apputils->check_user_session($user_session);
861 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
862 return OpenILS::Perm->new("COPY_CHECKIN");
865 my $session = $apputils->start_db_session();
868 my $orig_copy_status;
873 warn "retrieving copy for checkin\n";
876 my $copy_req = $session->request(
877 "open-ils.storage.direct.asset.copy.search.barcode.atomic",
879 $copy = $copy_req->gather(1)->[0];
881 $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
885 if($copy->status eq "3") { #if copy is lost
889 if($copy->status eq "6") { #copy is in transit, deal with it
890 my $method = $self->method_lookup("open-ils.circ.transit.receive");
891 ($transit_return) = $method->run( $user_session, $copy->id );
896 if(!$shelving_locations) {
897 my $sh_req = $session->request(
898 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
899 $shelving_locations = $sh_req->gather(1);
900 $shelving_locations =
901 { map { (''.$_->id => $_->name) } @$shelving_locations };
905 $orig_copy_status = $copy->status;
908 # find circ's where the transaction is still open for the
909 # given copy. should only be one.
910 warn "Retrieving circ for checkin\n";
911 my $circ_req = $session->request(
912 "open-ils.storage.direct.action.circulation.search.atomic",
913 { target_copy => $copy->id, xact_finish => undef },
914 { order_by => "xact_start desc", limit => 1 } );
916 $circ = $circ_req->gather(1)->[0];
920 $err = "No circulation exists for the given barcode";
924 $transaction = $session->request(
925 "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
927 warn "Checking in circ ". $circ->id . "\n";
929 $circ->stop_fines("CHECKIN");
930 $circ->stop_fines("RENEW") if($isrenewal);
931 $circ->stop_fines("LOST") if($iamlost);
932 $circ->xact_finish("now") if($transaction->balance_owed <= 0 and !$iamlost);
935 $circ->xact_finish($backdate);
937 # void any bills the resulted after the backdate time
938 my $bills = $session->request(
939 "open-ils.storage.direct.money.billing.search_where.atomic",
940 billing_ts => { ">=" => $backdate })->gather(1);
943 for my $bill (@$bills) {
946 my $s = $session->request(
947 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
950 throw OpenSRF::EX::ERROR
951 ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
957 my $cp_up = $session->request(
958 "open-ils.storage.direct.asset.copy.update", $copy );
961 my $ci_up = $session->request(
962 "open-ils.storage.direct.action.circulation.update", $circ );
967 warn "Checkin succeeded\n";
973 $err = "Error checking in: $e";
976 if($transit_return) { return $transit_return; }
980 return { record => undef, status => -1, text => $err };
985 # see if this copy can fulfill a hold
986 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
988 $route_to = $shelving_locations->{$copy->location};
991 warn "We found a hold that can be fulfilled by copy " . $copy->id . "\n";
993 $status_text = "Copy needed to fulfill hold";
994 $route_to = $hold->pickup_lib;
999 $status_text = "Copy is marked as LOST";
1002 if(!$hold and $copy->circ_lib ne $user->home_ou) {
1004 warn "Checked in copy needs to be transited " . $copy->id . "\n";
1006 my $transit = Fieldmapper::action::transit_copy->new;
1007 $transit->source($user->home_ou);
1008 $transit->dest($copy->circ_lib);
1009 $transit->target_copy($copy->id);
1010 $transit->source_send_time("now");
1011 $transit->copy_status($orig_copy_status);
1013 my $s = $session->request(
1014 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1016 if(!$s){ throw OpenSRF::EX::ERROR
1017 ("Unable to create new transit for copy " . $copy->id ); }
1019 warn "Putting copy into in transit state \n";
1021 $copy->editor($user->id);
1022 $copy->edit_date("now");
1024 $s = $session->request(
1025 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
1026 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
1029 $status_text = "Copy needs to be routed to a different location";
1030 $route_to = $copy->circ_lib;
1035 $apputils->commit_db_session($session);
1037 my $record = $apputils->simple_scalar_request(
1039 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1042 my $u = OpenILS::Utils::ModsParser->new();
1043 $u->start_mods_batch( $record->marc() );
1044 my $mods = $u->finish_mods_batch();
1049 text => $status_text,
1052 route_to => $route_to,
1064 # ------------------------------------------------------------------------------
1066 # ------------------------------------------------------------------------------
1069 __PACKAGE__->register_method(
1071 api_name => "open-ils.circ.renew",
1072 notes => <<" NOTES");
1073 open-ils.circ.renew(login_session, circ_object);
1074 Renews the provided circulation. login_session is the requestor of the
1075 renewal and if the logged in user is not the same as circ->usr, then
1076 the logged in user must have RENEW_CIRC permissions.
1080 my($self, $client, $login_session, $circ) = @_;
1082 throw OpenSRF::EX::InvalidArg
1083 ("open-ils.circ.renew no circ") unless defined($circ);
1085 my $user = $apputils->check_user_session($login_session);
1087 my $session = OpenSRF::AppSession->create("open-ils.storage");
1088 my $copy = _grab_copy_by_id($session, $circ->target_copy);
1090 my $r = $session->request(
1091 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
1092 $copy->id )->gather(1);
1094 my @holdids = map { $_->hold } @$r;
1098 my $holds = $session->request(
1099 "open-ils.storage.direct.action.hold_request.search_where",
1100 { id => \@holdids, current_copy => undef } )->gather(1);
1102 if( $holds and $user->id ne $circ->usr ) {
1103 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
1104 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
1108 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex;
1113 $circ = $session->request(
1114 "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
1117 my $iid = $circ->id;
1118 warn "Attempting to renew circ " . $iid . "\n";
1120 if($user->id ne $circ->usr) {
1121 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
1122 return OpenILS::Perm->new("RENEW_CIRC");
1126 if($circ->renewal_remaining <= 0) {
1127 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1131 # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1132 # XXX check overdue..?
1134 my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1135 my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1136 return $status if ($status->{status} ne "0");
1137 warn "Renewal checkin completed for $iid\n";
1139 my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1140 ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1141 return $status if($status->{status} ne "0");
1142 warn "Renewal permit checkout completed for $iid\n";
1144 my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1145 ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1146 warn "Renewal checkout completed for $iid\n";