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);
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.permission.grp_tree.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.... and setting num renews to " . $numrenews - 1 . "\n";
475 $circ->opac_renewal(1); # XXX different for different types !
477 $circ->renewal_remaining($numrenews - 1);
478 $circ->circ_staff($patron);
480 $circ->circ_staff($user->id);
484 # commit new circ object to db
485 my $commit = $session->request(
486 "open-ils.storage.direct.action.circulation.create", $circ );
487 my $id = $commit->gather(1);
490 throw OpenSRF::EX::ERROR
491 ("Error creating new circulation object");
494 # update the copy with the new circ
495 $copy->status( $stash->get("target_copy_status") );
496 $copy->location( $copy->location->id );
497 $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
500 my $copy_update = $session->request(
501 "open-ils.storage.direct.asset.copy.update",
503 $copy_update->gather(1);
506 if( $realstatus eq "8" ) { # on holds shelf
509 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
511 my $hold = $session->request(
512 "open-ils.storage.direct.action.hold_request.search.atomic",
513 current_copy => $copy->id , fulfillment_time => undef )->gather(1);
518 $hold->fulfillment_time("now");
519 my $s = $session->request(
520 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
521 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
525 $apputils->commit_db_session($session);
527 # remove our circ object from the cache
528 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
530 # re-retrieve the the committed circ object
531 $circ = $apputils->simple_scalar_request(
533 "open-ils.storage.direct.action.circulation.retrieve",
537 # push the rules and due date into the circ object
538 $circ->duration_rule($duration);
539 $circ->max_fine_rule($max);
540 $circ->recuring_fine_rule($recurring);
542 # turn the biblio record into a friendly object
543 my $obj = $stash->get("circ_objects");
544 my $u = OpenILS::Utils::ModsParser->new();
545 $u->start_mods_batch( $circ_objects->{title}->marc() );
546 my $mods = $u->finish_mods_batch();
549 return { circ => $circ, copy => $copy, record => $mods };
555 # runs the duration, recurring_fines, and max_fines scripts.
556 # builds the new circ object based on the rules returned from
558 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
559 sub run_circ_scripts {
562 # go through all of the scripts and process
563 # each script returns
564 # [ rule_name, level (appropriate to the script) ]
565 $stash->set("result", [] );
566 $stash->set("run_block", $duration_script);
568 my $duration_rule = $stash->get("result");
570 $stash->set("run_block", $recurring_fines_script);
571 $stash->set("result", [] );
573 my $rec_fines_rule = $stash->get("result");
575 $stash->set("run_block", $max_fines_script);
576 $stash->set("result", [] );
578 my $max_fines_rule = $stash->get("result");
580 my $obj = $stash->get("circ_objects");
582 # ----------------------------------------------------------
583 # find the rules objects based on the rule names returned from
584 # the various scripts.
585 my $dur_req = $session->request(
586 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
587 $duration_rule->[0] );
589 my $rec_req = $session->request(
590 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
591 $rec_fines_rule->[0] );
593 my $max_req = $session->request(
594 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
595 $max_fines_rule->[0] );
597 my $duration = $dur_req->gather(1)->[0];
598 my $recurring = $rec_req->gather(1)->[0];
599 my $max = $max_req->gather(1)->[0];
601 my $copy = $circ_objects->{copy};
604 warn "Building a new circulation object with\n".
605 "=> copy " . Dumper($copy) .
606 "=> duration_rule " . Dumper($duration_rule) .
607 "=> rec_files_rule " . Dumper($rec_fines_rule) .
608 "=> duration " . Dumper($duration) .
609 "=> recurring " . Dumper($recurring) .
610 "=> max " . Dumper($max);
613 # build the new circ object
614 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
615 $rec_fines_rule->[1], $duration, $recurring, $max );
617 return ($circ, $duration, $recurring, $max);
621 # ------------------------------------------------------------------
622 # Builds a new circ object
623 # ------------------------------------------------------------------
624 sub build_circ_object {
625 my( $session, $copy, $dur_level, $rec_level,
626 $duration, $recurring, $max ) = @_;
628 my $circ = new Fieldmapper::action::circulation;
630 $circ->circ_lib( $copy->circ_lib->id() );
631 if($dur_level == 1) {
632 $circ->duration( $duration->shrt );
633 } elsif($dur_level == 2) {
634 $circ->duration( $duration->normal );
635 } elsif($dur_level == 3) {
636 $circ->duration( $duration->extended );
639 if($rec_level == 1) {
640 $circ->recuring_fine( $recurring->low );
641 } elsif($rec_level == 2) {
642 $circ->recuring_fine( $recurring->normal );
643 } elsif($rec_level == 3) {
644 $circ->recuring_fine( $recurring->high );
647 $circ->duration_rule( $duration->name );
648 $circ->recuring_fine_rule( $recurring->name );
649 $circ->max_fine_rule( $max->name );
650 $circ->max_fine( $max->amount );
652 $circ->fine_interval($recurring->recurance_interval);
653 $circ->renewal_remaining( $duration->max_renewals );
654 $circ->target_copy( $copy->id );
655 $circ->usr( $circ_objects->{patron}->id );
661 __PACKAGE__->register_method(
662 method => "transit_receive",
663 api_name => "open-ils.circ.transit.receive",
664 notes => <<" NOTES");
665 Receives a copy that is in transit.
666 Params are login_session and copyid.
667 Logged in user must have COPY_CHECKIN priveleges.
669 status 3 means that this transit is destined for somewhere else
670 status 10 means the copy is not in transit
671 status 11 means the transit is complete, does not need processing
672 status 12 means copy is in transit but no tansit was found
676 sub transit_receive {
677 my( $self, $client, $login_session, $copyid ) = @_;
679 my $user = $apputils->check_user_session($login_session);
681 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
682 return OpenILS::Perm->new("COPY_CHECKIN");
685 warn "Receiving copy for transit $copyid\n";
687 my $session = $apputils->start_db_session();
688 my $copy = $session->request(
689 "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
692 if(!$copy->status eq "6") {
693 return { status => 10, route_to => $copy->circ_lib };
697 $transit = $session->request(
698 "open-ils.storage.direct.action.transit_copy.search_where",
699 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
702 my $record = _grab_title_by_copy($session, $copy->id);
703 my $u = OpenILS::Utils::ModsParser->new();
704 $u->start_mods_batch( $record->marc() );
705 $record = $u->finish_mods_batch();
709 warn "Found transit " . $transit->id . " for copy $copyid\n";
711 if( defined($transit->dest_recv_time) ) {
712 return { status => 11, route_to => $copy->circ_lib,
713 text => "Transit is already complete for this copy" };
716 $transit->dest_recv_time("now");
717 my $s = $session->request(
718 "open-ils.storage.direct.action.transit_copy.update", $transit )->gather(1);
720 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating transit " . $transit->id . "\n"); }
722 warn "Searching for hold transit with id " . $transit->id . "\n";
724 my $holdtransit = $session->request(
725 "open-ils.storage.direct.action.hold_transit_copy.retrieve",
726 $transit->id )->gather(1);
730 warn "Found hold transit for copy $copyid\n";
732 my $hold = $session->request(
733 "open-ils.storage.direct.action.hold_request.retrieve",
734 $holdtransit->hold )->gather(1);
737 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
740 # put copy on the holds shelf
741 $copy->status(8); #hold shelf status
742 $copy->editor($user->id); #hold shelf status
743 $copy->edit_date("now"); #hold shelf status
745 warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
747 my $s = $session->request(
748 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
749 if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
751 $apputils->commit_db_session($session);
753 return { status => 4, route_to => "Holds Shelf",
754 text => "Transit Complete", record => $record, copy => $copy };
759 if($transit->dest eq $user->home_ou) {
762 $copy->editor($user->id);
763 $copy->edit_date("now");
765 my $s = $session->request(
766 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
767 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
769 my($status, $status_text) = (0, "Transit Complete");
773 if($transit->copy_status eq "3") { #if copy is lost
775 $status_text = "Copy is marked as LOST";
777 $circ = $session->request(
778 "open-ils.storage.direct.action.circulation.search_where",
779 { target_copy => $copy->id, xact_finish => undef },
780 { order_by => "xact_start desc", limit => 1 } )->gather(1);
785 my $transaction = $session->request(
786 "open-ils.storage.direct.money.open_billable_transaction_summary.retrieve", $circ->id)->gather(1);
788 $circ->xact_finish("now") if($transaction->balance_owed <= 0);
790 my $s = $session->request(
791 "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
794 throw OpenSRF::EX::ERROR ("Error updating circulation " . $circ->id);
800 $apputils->commit_db_session($session);
804 route_to => $user->home_ou,
805 text => $status_text,
812 $apputils->rollback_db_session($session);
816 copy => $copy, record => $record,
817 status => 3, route_to => $transit->dest,
818 text => "Transit needs to be routed" };
827 $apputils->rollback_db_session($session);
828 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
835 __PACKAGE__->register_method(
837 api_name => "open-ils.circ.checkin.barcode",
838 notes => <<" NOTES");
839 Checks in based on barcode
840 Returns record, status, text, circ, copy, route_to
843 1 = 'copy required to fulfil a hold'
844 2 = "copy is marked as lost"
846 4 = "transit for hold complete, put on holds shelf"
850 my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
858 my $status_text = "OK";
862 my $user = $apputils->check_user_session($user_session);
864 if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
865 return OpenILS::Perm->new("COPY_CHECKIN");
868 my $session = $apputils->start_db_session();
871 my $orig_copy_status;
876 warn "retrieving copy for checkin\n";
879 my $copy_req = $session->request(
880 "open-ils.storage.direct.asset.copy.search.barcode.atomic",
882 $copy = $copy_req->gather(1)->[0];
884 $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
888 if($copy->status eq "3") { #if copy is lost
892 if($copy->status eq "6") { #copy is in transit, deal with it
893 my $method = $self->method_lookup("open-ils.circ.transit.receive");
894 ($transit_return) = $method->run( $user_session, $copy->id );
899 if(!$shelving_locations) {
900 my $sh_req = $session->request(
901 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
902 $shelving_locations = $sh_req->gather(1);
903 $shelving_locations =
904 { map { (''.$_->id => $_->name) } @$shelving_locations };
908 $orig_copy_status = $copy->status;
911 # find circ's where the transaction is still open for the
912 # given copy. should only be one.
913 warn "Retrieving circ for checkin\n";
914 my $circ_req = $session->request(
915 "open-ils.storage.direct.action.circulation.search.atomic",
916 { target_copy => $copy->id, xact_finish => undef },
917 { order_by => "xact_start desc", limit => 1 } );
919 $circ = $circ_req->gather(1)->[0];
923 $err = "No circulation exists for the given barcode";
927 $transaction = $session->request(
928 "open-ils.storage.direct.money.open_billable_transaction_summary.retrieve", $circ->id)->gather(1);
930 warn "Checking in circ ". $circ->id . "\n";
932 $circ->stop_fines("CHECKIN");
933 $circ->stop_fines("RENEW") if($isrenewal);
934 $circ->stop_fines("LOST") if($iamlost);
935 $circ->xact_finish("now") if($transaction->balance_owed <= 0 and !$iamlost);
936 $circ->stop_fines_time('now');
937 $circ->checkin_time('now');
938 $circ->checkin_staff($user->id);
941 $circ->xact_finish($backdate);
943 # void any bills the resulted after the backdate time
944 my $bills = $session->request(
945 "open-ils.storage.direct.money.billing.search_where.atomic",
946 billing_ts => { ">=" => $backdate })->gather(1);
949 for my $bill (@$bills) {
952 my $s = $session->request(
953 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
956 throw OpenSRF::EX::ERROR
957 ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
963 my $cp_up = $session->request(
964 "open-ils.storage.direct.asset.copy.update", $copy );
967 my $ci_up = $session->request(
968 "open-ils.storage.direct.action.circulation.update", $circ );
973 warn "Checkin succeeded\n";
979 $err = "Error checking in: $e";
982 if($transit_return) { return $transit_return; }
986 return { record => undef, status => -1, text => $err };
991 # see if this copy can fulfill a hold
992 my ( $hold, $evt ) = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
994 $route_to = $shelving_locations->{$copy->location};
997 warn "We found a hold that can be fulfilled by copy " . $copy->id . "\n";
999 $status_text = "Copy needed to fulfill hold";
1000 $route_to = $hold->pickup_lib;
1005 $status_text = "Copy is marked as LOST";
1008 if(!$hold and $copy->circ_lib ne $user->home_ou) {
1010 warn "Checked in copy needs to be transited " . $copy->id . "\n";
1012 my $transit = Fieldmapper::action::transit_copy->new;
1013 $transit->source($user->home_ou);
1014 $transit->dest($copy->circ_lib);
1015 $transit->target_copy($copy->id);
1016 $transit->source_send_time("now");
1017 $transit->copy_status($orig_copy_status);
1019 my $s = $session->request(
1020 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1022 if(!$s){ throw OpenSRF::EX::ERROR
1023 ("Unable to create new transit for copy " . $copy->id ); }
1025 warn "Putting copy into in transit state \n";
1027 $copy->editor($user->id);
1028 $copy->edit_date("now");
1030 $s = $session->request(
1031 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
1032 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
1035 $status_text = "Copy needs to be routed to a different location";
1036 $route_to = $copy->circ_lib;
1041 $apputils->commit_db_session($session);
1043 my $record = $apputils->simple_scalar_request(
1045 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1049 if( $record->marc ) {
1050 my $u = OpenILS::Utils::ModsParser->new();
1051 $u->start_mods_batch( $record->marc() );
1052 $mods = $u->finish_mods_batch();
1058 text => $status_text,
1061 route_to => $route_to,
1073 # ------------------------------------------------------------------------------
1075 # ------------------------------------------------------------------------------
1078 __PACKAGE__->register_method(
1080 api_name => "open-ils.circ.renew",
1081 notes => <<" NOTES");
1082 open-ils.circ.renew(login_session, circ_object);
1083 Renews the provided circulation. login_session is the requestor of the
1084 renewal and if the logged in user is not the same as circ->usr, then
1085 the logged in user must have RENEW_CIRC permissions.
1089 my($self, $client, $login_session, $circ) = @_;
1093 throw OpenSRF::EX::InvalidArg
1094 ("open-ils.circ.renew no circ") unless defined($circ);
1096 ($user, $evt) = $apputils->checkses($login_session);
1097 return $evt if $evt;
1099 my $session = OpenSRF::AppSession->create("open-ils.storage");
1100 my $copy = _grab_copy_by_id($session, $circ->target_copy);
1102 my $r = $session->request(
1103 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
1104 $copy->id )->gather(1);
1106 my @holdids = map { $_->hold } @$r;
1110 my $holds = $session->request(
1111 "open-ils.storage.direct.action.hold_request.search_where",
1112 { id => \@holdids, current_copy => undef } )->gather(1);
1114 if( $holds and $user->id ne $circ->usr ) {
1115 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
1116 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
1120 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex;
1125 $circ = $session->request(
1126 "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
1129 my $iid = $circ->id;
1130 warn "Attempting to renew circ " . $iid . "\n";
1132 if($user->id ne $circ->usr) {
1133 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
1134 return OpenILS::Perm->new("RENEW_CIRC");
1138 if($circ->renewal_remaining <= 0) {
1139 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1143 # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1144 # XXX check overdue..?
1147 my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1148 my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1149 warn 'checkin status: ' . Dumper($status) . '\n';
1150 return $status if ref($status) eq "Fieldmapper::perm_ex";
1151 return $status if ($status->{status} ne "0");
1152 warn "Renewal checkin completed for $iid\n";
1154 my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1155 ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1156 warn 'permit checkout status: ' . Dumper($status) . '\n';
1157 return $status if ref($status) eq "Fieldmapper::perm_ex";
1158 return $status if($status->{status} ne "0");
1159 warn "Renewal permit checkout completed for $iid\n";
1161 my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1162 ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1163 warn 'renew checkout status: ' . Dumper($status) . '\n';
1164 return $status if ref($status) eq "Fieldmapper::perm_ex";
1165 warn "Renewal checkout completed for $iid\n";