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 "open-ils.storage.direct.permission.grp_tree.retrieve.all.atomic");
126 $patron_profiles = $profile_req->gather(1);
128 { map { (''.$_->id => $_->name) } @$patron_profiles };
135 my $patron_id = shift;
136 my $patron_req = $session->request(
137 "open-ils.storage.direct.actor.user.retrieve",
139 return $patron_req->gather(1);
143 sub _grab_title_by_copy {
146 my $title_req = $session->request(
147 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
149 return $title_req->gather(1);
152 sub _grab_patron_summary {
154 my $patron_id = shift;
155 my $summary_req = $session->request(
156 "open-ils.storage.action.circulation.patron_summary",
158 return $summary_req->gather(1);
161 sub _grab_copy_by_barcode {
162 my($session, $barcode) = @_;
163 warn "Searching for copy with barcode $barcode\n";
164 my $copy_req = $session->request(
165 "open-ils.storage.fleshed.asset.copy.search.barcode", $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) unless ref($copy->circ_lib);
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} = $$args{requestor} || _grab_user($session, $hold->requestor);
197 my $patron = $$args{usr} || _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.... and setting num renews to " . $numrenews - 1 . "\n";
476 $circ->opac_renewal(1); # XXX different for different types !
478 $circ->renewal_remaining($numrenews - 1);
479 $circ->circ_staff($patron);
481 $circ->circ_staff($user->id);
485 # commit new circ object to db
486 my $commit = $session->request(
487 "open-ils.storage.direct.action.circulation.create", $circ );
488 my $id = $commit->gather(1);
491 throw OpenSRF::EX::ERROR
492 ("Error creating new circulation object");
495 # update the copy with the new circ
496 $copy->status( $stash->get("target_copy_status") );
497 $copy->location( $copy->location->id );
498 $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
501 my $copy_update = $session->request(
502 "open-ils.storage.direct.asset.copy.update",
504 $copy_update->gather(1);
507 if( $realstatus eq "8" ) { # on holds shelf
510 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
512 my $hold = $session->request(
513 "open-ils.storage.direct.action.hold_request.search.atomic",
514 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
519 $hold->fulfillment_time("now");
520 my $s = $session->request(
521 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
522 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
526 $apputils->commit_db_session($session);
528 # remove our circ object from the cache
529 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
531 # re-retrieve the the committed circ object
532 $circ = $apputils->simple_scalar_request(
534 "open-ils.storage.direct.action.circulation.retrieve",
538 # push the rules and due date into the circ object
539 $circ->duration_rule($duration);
540 $circ->max_fine_rule($max);
541 $circ->recuring_fine_rule($recurring);
543 # turn the biblio record into a friendly object
544 my $obj = $stash->get("circ_objects");
545 my $u = OpenILS::Utils::ModsParser->new();
546 $u->start_mods_batch( $circ_objects->{title}->marc() );
547 my $mods = $u->finish_mods_batch();
550 return { circ => $circ, copy => $copy, record => $mods };
556 # runs the duration, recurring_fines, and max_fines scripts.
557 # builds the new circ object based on the rules returned from
559 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
560 sub run_circ_scripts {
563 # go through all of the scripts and process
564 # each script returns
565 # [ rule_name, level (appropriate to the script) ]
566 $stash->set("result", [] );
567 $stash->set("run_block", $duration_script);
569 my $duration_rule = $stash->get("result");
571 $stash->set("run_block", $recurring_fines_script);
572 $stash->set("result", [] );
574 my $rec_fines_rule = $stash->get("result");
576 $stash->set("run_block", $max_fines_script);
577 $stash->set("result", [] );
579 my $max_fines_rule = $stash->get("result");
581 my $obj = $stash->get("circ_objects");
583 # ----------------------------------------------------------
584 # find the rules objects based on the rule names returned from
585 # the various scripts.
586 my $dur_req = $session->request(
587 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
588 $duration_rule->[0] );
590 my $rec_req = $session->request(
591 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
592 $rec_fines_rule->[0] );
594 my $max_req = $session->request(
595 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
596 $max_fines_rule->[0] );
598 my $duration = $dur_req->gather(1)->[0];
599 my $recurring = $rec_req->gather(1)->[0];
600 my $max = $max_req->gather(1)->[0];
602 my $copy = $circ_objects->{copy};
605 warn "Building a new circulation object with\n".
606 "=> copy " . Dumper($copy) .
607 "=> duration_rule " . Dumper($duration_rule) .
608 "=> rec_files_rule " . Dumper($rec_fines_rule) .
609 "=> duration " . Dumper($duration) .
610 "=> recurring " . Dumper($recurring) .
611 "=> max " . Dumper($max);
614 # build the new circ object
615 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
616 $rec_fines_rule->[1], $duration, $recurring, $max );
618 return ($circ, $duration, $recurring, $max);
622 # ------------------------------------------------------------------
623 # Builds a new circ object
624 # ------------------------------------------------------------------
625 sub build_circ_object {
626 my( $session, $copy, $dur_level, $rec_level,
627 $duration, $recurring, $max ) = @_;
629 my $circ = new Fieldmapper::action::circulation;
631 $circ->circ_lib( $copy->circ_lib->id() );
632 if($dur_level == 1) {
633 $circ->duration( $duration->shrt );
634 } elsif($dur_level == 2) {
635 $circ->duration( $duration->normal );
636 } elsif($dur_level == 3) {
637 $circ->duration( $duration->extended );
640 if($rec_level == 1) {
641 $circ->recuring_fine( $recurring->low );
642 } elsif($rec_level == 2) {
643 $circ->recuring_fine( $recurring->normal );
644 } elsif($rec_level == 3) {
645 $circ->recuring_fine( $recurring->high );
648 $circ->duration_rule( $duration->name );
649 $circ->recuring_fine_rule( $recurring->name );
650 $circ->max_fine_rule( $max->name );
651 $circ->max_fine( $max->amount );
653 $circ->fine_interval($recurring->recurance_interval);
654 $circ->renewal_remaining( $duration->max_renewals );
655 $circ->target_copy( $copy->id );
656 $circ->usr( $circ_objects->{patron}->id );
662 __PACKAGE__->register_method(
663 method => "transit_receive",
664 api_name => "open-ils.circ.transit.receive",
665 notes => <<" NOTES");
666 Receives a copy that is in transit.
667 Params are login_session and copyid.
668 Logged in user must have COPY_CHECKIN priveleges.
670 status 3 means that this transit is destined for somewhere else
671 status 10 means the copy is not in transit
672 status 11 means the transit is complete, does not need processing
673 status 12 means copy is in transit but no tansit was found
677 sub transit_receive {
678 my( $self, $client, $login_session, $copyid ) = @_;
680 my $user = $apputils->check_user_session($login_session);
682 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
683 return OpenILS::Perm->new("COPY_CHECKIN");
686 warn "Receiving copy for transit $copyid\n";
688 my $session = $apputils->start_db_session();
689 my $copy = $session->request(
690 "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
693 if(!$copy->status eq "6") {
694 return { status => 10, route_to => $copy->circ_lib };
698 $transit = $session->request(
699 "open-ils.storage.direct.action.transit_copy.search_where",
700 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
703 my $record = _grab_title_by_copy($session, $copy->id);
704 my $u = OpenILS::Utils::ModsParser->new();
705 $u->start_mods_batch( $record->marc() );
706 $record = $u->finish_mods_batch();
710 warn "Found transit " . $transit->id . " for copy $copyid\n";
712 if( defined($transit->dest_recv_time) ) {
713 return { status => 11, route_to => $copy->circ_lib,
714 text => "Transit is already complete for this copy" };
717 $transit->dest_recv_time("now");
718 my $s = $session->request(
719 "open-ils.storage.direct.action.transit_copy.update", $transit )->gather(1);
721 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating transit " . $transit->id . "\n"); }
723 warn "Searching for hold transit with id " . $transit->id . "\n";
725 my $holdtransit = $session->request(
726 "open-ils.storage.direct.action.hold_transit_copy.retrieve",
727 $transit->id )->gather(1);
731 warn "Found hold transit for copy $copyid\n";
733 my $hold = $session->request(
734 "open-ils.storage.direct.action.hold_request.retrieve",
735 $holdtransit->hold )->gather(1);
738 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
741 # put copy on the holds shelf
742 $copy->status(8); #hold shelf status
743 $copy->editor($user->id); #hold shelf status
744 $copy->edit_date("now"); #hold shelf status
746 warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
748 my $s = $session->request(
749 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
750 if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
752 $apputils->commit_db_session($session);
754 return { status => 4, route_to => "Holds Shelf",
755 text => "Transit Complete", record => $record, copy => $copy };
760 if($transit->dest eq $user->home_ou) {
763 $copy->editor($user->id);
764 $copy->edit_date("now");
766 my $s = $session->request(
767 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
768 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
770 my($status, $status_text) = (0, "Transit Complete");
774 if($transit->copy_status eq "3") { #if copy is lost
776 $status_text = "Copy is marked as LOST";
778 $circ = $session->request(
779 "open-ils.storage.direct.action.circulation.search_where",
780 { target_copy => $copy->id, xact_finish => undef },
781 { order_by => "xact_start desc", limit => 1 } )->gather(1);
786 my $transaction = $session->request(
787 "open-ils.storage.direct.money.open_billable_transaction_summary.retrieve", $circ->id)->gather(1);
789 $circ->xact_finish("now") if($transaction->balance_owed <= 0);
791 my $s = $session->request(
792 "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
795 throw OpenSRF::EX::ERROR ("Error updating circulation " . $circ->id);
801 $apputils->commit_db_session($session);
805 route_to => $user->home_ou,
806 text => $status_text,
813 $apputils->rollback_db_session($session);
817 copy => $copy, record => $record,
818 status => 3, route_to => $transit->dest,
819 text => "Transit needs to be routed" };
828 $apputils->rollback_db_session($session);
829 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
836 __PACKAGE__->register_method(
838 api_name => "open-ils.circ.checkin.barcode",
839 notes => <<" NOTES");
840 Checks in based on barcode
841 Returns record, status, text, circ, copy, route_to
844 1 = 'copy required to fulfil a hold'
845 2 = "copy is marked as lost"
847 4 = "transit for hold complete, put on holds shelf"
851 my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
859 my $status_text = "OK";
863 my $user = $apputils->check_user_session($user_session);
865 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
866 return OpenILS::Perm->new("COPY_CHECKIN");
869 my $session = $apputils->start_db_session();
872 my $orig_copy_status;
877 warn "retrieving copy for checkin\n";
880 my $copy_req = $session->request(
881 "open-ils.storage.direct.asset.copy.search.barcode.atomic",
883 $copy = $copy_req->gather(1)->[0];
885 $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
889 if($copy->status eq "3") { #if copy is lost
893 if($copy->status eq "6") { #copy is in transit, deal with it
894 my $method = $self->method_lookup("open-ils.circ.transit.receive");
895 ($transit_return) = $method->run( $user_session, $copy->id );
900 if(!$shelving_locations) {
901 my $sh_req = $session->request(
902 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
903 $shelving_locations = $sh_req->gather(1);
904 $shelving_locations =
905 { map { (''.$_->id => $_->name) } @$shelving_locations };
909 $orig_copy_status = $copy->status;
912 # find circ's where the transaction is still open for the
913 # given copy. should only be one.
914 warn "Retrieving circ for checkin\n";
915 my $circ_req = $session->request(
916 "open-ils.storage.direct.action.circulation.search.atomic",
917 { target_copy => $copy->id, xact_finish => undef },
918 { order_by => "xact_start desc", limit => 1 } );
920 $circ = $circ_req->gather(1)->[0];
924 $err = "No circulation exists for the given barcode";
928 $transaction = $session->request(
929 "open-ils.storage.direct.money.open_billable_transaction_summary.retrieve", $circ->id)->gather(1);
931 warn "Checking in circ ". $circ->id . "\n";
933 $circ->stop_fines("CHECKIN");
934 $circ->stop_fines("RENEW") if($isrenewal);
935 $circ->stop_fines("LOST") if($iamlost);
936 $circ->xact_finish("now") if($transaction->balance_owed <= 0 and !$iamlost);
937 $circ->stop_fines_time('now');
938 $circ->checkin_time('now');
939 $circ->checkin_staff($user->id);
942 $circ->xact_finish($backdate);
944 # void any bills the resulted after the backdate time
945 my $bills = $session->request(
946 "open-ils.storage.direct.money.billing.search_where.atomic",
947 billing_ts => { ">=" => $backdate })->gather(1);
950 for my $bill (@$bills) {
953 my $s = $session->request(
954 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
957 throw OpenSRF::EX::ERROR
958 ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
964 my $cp_up = $session->request(
965 "open-ils.storage.direct.asset.copy.update", $copy );
968 my $ci_up = $session->request(
969 "open-ils.storage.direct.action.circulation.update", $circ );
974 warn "Checkin succeeded\n";
980 $err = "Error checking in: $e";
983 if($transit_return) { return $transit_return; }
987 return { record => undef, status => -1, text => $err };
992 # see if this copy can fulfill a hold
993 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
995 $route_to = $shelving_locations->{$copy->location};
998 warn "We found a hold that can be fulfilled by copy " . $copy->id . "\n";
1000 $status_text = "Copy needed to fulfill hold";
1001 $route_to = $hold->pickup_lib;
1006 $status_text = "Copy is marked as LOST";
1009 if(!$hold and $copy->circ_lib ne $user->home_ou) {
1011 warn "Checked in copy needs to be transited " . $copy->id . "\n";
1013 my $transit = Fieldmapper::action::transit_copy->new;
1014 $transit->source($user->home_ou);
1015 $transit->dest($copy->circ_lib);
1016 $transit->target_copy($copy->id);
1017 $transit->source_send_time("now");
1018 $transit->copy_status($orig_copy_status);
1020 my $s = $session->request(
1021 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1023 if(!$s){ throw OpenSRF::EX::ERROR
1024 ("Unable to create new transit for copy " . $copy->id ); }
1026 warn "Putting copy into in transit state \n";
1028 $copy->editor($user->id);
1029 $copy->edit_date("now");
1031 $s = $session->request(
1032 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
1033 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
1036 $status_text = "Copy needs to be routed to a different location";
1037 $route_to = $copy->circ_lib;
1042 $apputils->commit_db_session($session);
1044 my $record = $apputils->simple_scalar_request(
1046 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1049 my $u = OpenILS::Utils::ModsParser->new();
1050 $u->start_mods_batch( $record->marc() );
1051 my $mods = $u->finish_mods_batch();
1056 text => $status_text,
1059 route_to => $route_to,
1071 # ------------------------------------------------------------------------------
1073 # ------------------------------------------------------------------------------
1076 __PACKAGE__->register_method(
1078 api_name => "open-ils.circ.renew",
1079 notes => <<" NOTES");
1080 open-ils.circ.renew(login_session, circ_object);
1081 Renews the provided circulation. login_session is the requestor of the
1082 renewal and if the logged in user is not the same as circ->usr, then
1083 the logged in user must have RENEW_CIRC permissions.
1087 my($self, $client, $login_session, $circ) = @_;
1089 throw OpenSRF::EX::InvalidArg
1090 ("open-ils.circ.renew no circ") unless defined($circ);
1092 my $user = $apputils->check_user_session($login_session);
1094 my $session = OpenSRF::AppSession->create("open-ils.storage");
1095 my $copy = _grab_copy_by_id($session, $circ->target_copy);
1097 my $r = $session->request(
1098 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
1099 $copy->id )->gather(1);
1101 my @holdids = map { $_->hold } @$r;
1105 my $holds = $session->request(
1106 "open-ils.storage.direct.action.hold_request.search_where",
1107 { id => \@holdids, current_copy => undef } )->gather(1);
1109 if( $holds and $user->id ne $circ->usr ) {
1110 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
1111 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
1115 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex;
1120 $circ = $session->request(
1121 "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
1124 my $iid = $circ->id;
1125 warn "Attempting to renew circ " . $iid . "\n";
1127 if($user->id ne $circ->usr) {
1128 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
1129 return OpenILS::Perm->new("RENEW_CIRC");
1133 if($circ->renewal_remaining <= 0) {
1134 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1138 # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1139 # XXX check overdue..?
1142 my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1143 my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1144 warn 'checkin status: ' . Dumper($status) . '\n';
1145 return $status if ref($status) eq "Fieldmapper::perm_ex";
1146 return $status if ($status->{status} ne "0");
1147 warn "Renewal checkin completed for $iid\n";
1149 my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1150 ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1151 warn 'permit checkout status: ' . Dumper($status) . '\n';
1152 return $status if ref($status) eq "Fieldmapper::perm_ex";
1153 return $status if($status->{status} ne "0");
1154 warn "Renewal permit checkout completed for $iid\n";
1156 my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1157 ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1158 warn 'renew checkout status: ' . Dumper($status) . '\n';
1159 return $status if ref($status) eq "Fieldmapper::perm_ex";
1160 warn "Renewal checkout completed for $iid\n";