]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
more holds and transit mangling
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Rules.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2005  Georgia Public Library Service 
3 # Bill Erickson <highfalutin@gmail.com>
4
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.
9
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 # ---------------------------------------------------------------
15
16 package OpenILS::Application::Circ::Rules;
17 use base qw/OpenSRF::Application/;
18 use strict; use warnings;
19
20 use OpenSRF::Utils::SettingsClient;
21 use OpenILS::Utils::Fieldmapper;
22 use OpenILS::EX;
23 use OpenSRF::Utils::Logger qw(:level); 
24
25 use Template qw(:template);
26 use Template::Stash; 
27
28 use Time::HiRes qw(time);
29 use OpenILS::Utils::ModsParser;
30
31 use OpenSRF::Utils;
32 use OpenSRF::EX qw(:try);
33
34 use OpenILS::Application::AppUtils;
35 my $apputils = "OpenILS::Application::AppUtils";
36 use Digest::MD5 qw(md5_hex);
37
38 my $log = "OpenSRF::Utils::Logger";
39
40 # ----------------------------------------------------------------
41 # rules scripts
42 my $circ_script;
43 my $permission_script;
44 my $duration_script;
45 my $recurring_fines_script;
46 my $max_fines_script;
47 my $permit_hold_script;
48 my $permit_renew_script;
49 # ----------------------------------------------------------------
50
51
52 # data used for this circulation transaction
53 my $circ_objects = {};
54
55 # some static data from the database
56 my $copy_statuses;
57 my $patron_standings;
58 my $patron_profiles;
59 my $shelving_locations;
60
61 # template stash
62 my $stash;
63
64 # memcache for caching the circ objects
65 my $cache_handle;
66
67
68 use constant NO_COPY => 100;
69
70 sub initialize {
71
72         my $self = shift;
73         my $conf = OpenSRF::Utils::SettingsClient->new;
74
75         # ----------------------------------------------------------------
76         # set up the rules scripts
77         # ----------------------------------------------------------------
78         $circ_script = $conf->config_value(                                     
79                 "apps", "open-ils.circ","app_settings", "rules", "main");
80
81         $permission_script = $conf->config_value(                       
82                 "apps", "open-ils.circ","app_settings", "rules", "permission");
83
84         $duration_script = $conf->config_value(                 
85                 "apps", "open-ils.circ","app_settings", "rules", "duration");
86
87         $recurring_fines_script = $conf->config_value(  
88                 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
89
90         $max_fines_script = $conf->config_value(                        
91                 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
92
93         $permit_hold_script = $conf->config_value(
94                 "apps", "open-ils.circ","app_settings", "rules", "permit_hold");
95
96         $permit_renew_script = $conf->config_value(
97                 "apps", "open-ils.circ","app_settings", "rules", "permit_renew");
98
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);
103
104
105         $cache_handle = OpenSRF::Utils::Cache->new();
106 }
107
108
109 sub _grab_patron_standings {
110         my $session = shift;
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);
115                 $patron_standings =
116                         { map { (''.$_->id => $_->value) } @$patron_standings };
117         }
118 }
119
120 sub _grab_patron_profiles {
121         my $session = shift;
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);
126                 $patron_profiles =
127                         { map { (''.$_->id => $_->name) } @$patron_profiles };
128         }
129
130 }
131
132 sub _grab_user {
133         my $session = shift;
134         my $patron_id = shift;
135         my $patron_req  = $session->request(
136                 "open-ils.storage.direct.actor.user.retrieve", 
137                 $patron_id );
138         return $patron_req->gather(1);
139 }
140         
141
142 sub _grab_title_by_copy {
143         my $session = shift;
144         my $copyid = shift;
145         my $title_req   = $session->request(
146                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
147                 $copyid );
148         return $title_req->gather(1);
149 }
150
151 sub _grab_patron_summary {
152         my $session = shift;
153         my $patron_id = shift;
154         my $summary_req = $session->request(
155                 "open-ils.storage.action.circulation.patron_summary",
156                 $patron_id );
157         return $summary_req->gather(1);
158 }
159
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", 
165                 $barcode );
166         return $copy_req->gather(1);
167 }
168
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", 
174                 $id );
175         my $c = $copy_req->gather(1);
176         if($c) { return _grab_copy_by_barcode($session, $c->barcode); }
177         return undef;
178 }
179
180
181 sub gather_hold_objects {
182         my($session, $hold, $copy, $args) = @_;
183
184         _grab_patron_standings($session);
185         _grab_patron_profiles($session);
186
187
188         # flesh me
189         $copy = _grab_copy_by_barcode($session, $copy->barcode);
190
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);
198
199         $copy->status( $copy->status->name );
200         $patron->standing($patron_standings->{$patron->standing()});
201         $patron->profile( $patron_profiles->{$patron->profile});
202
203         $hold_objects->{patron}         = $patron;
204
205         return $hold_objects;
206 }
207
208
209
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
215         NOTES
216
217 sub permit_hold {
218         my( $self, $client, $hold, $copy, $args ) = @_;
219
220         my $session     = OpenSRF::AppSession->create("open-ils.storage");
221         
222         # collect items necessary for circ calculation
223         my $hold_objects = gather_hold_objects( $session, $hold, $copy, $args );
224
225         $stash = Template::Stash->new(
226                         circ_objects                    => $hold_objects,
227                         result                                  => []);
228
229         $stash->set("run_block", $permit_hold_script);
230
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);
234         $summary->[0] ||= 0;
235         $summary->[1] ||= 0.0;
236
237         $stash->set("patron_copies", $summary->[0] );
238         $stash->set("patron_fines", $summary->[1] );
239
240         # run the permissibility script
241         run_script();
242         my $result = $stash->get("result");
243
244         # 0 means OK in the script
245         return 1 if($result->[0] == 0);
246         return 0;
247
248 }
249
250
251
252
253
254 # ----------------------------------------------------------------
255 # Collect all of the objects necessary for calculating the
256 # circ matrix.
257 # ----------------------------------------------------------------
258 sub gather_circ_objects {
259         my( $session, $barcode_string, $patron_id ) = @_;
260
261         throw OpenSRF::EX::ERROR 
262                 ("gather_circ_objects needs data")
263                         unless ($barcode_string and $patron_id);
264
265         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
266
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);
270
271         if($circ_objects) { 
272                 $stash = Template::Stash->new(
273                         circ_objects                    => $circ_objects,
274                         result                                  => [],
275                         target_copy_status      => 1,
276                         );
277                 return;
278         }
279
280         # only necessary if the circ objects have not been built yet
281
282         _grab_patron_standings($session);
283         _grab_patron_profiles($session);
284
285
286         my $copy = _grab_copy_by_barcode($session, $barcode_string);
287         if(!$copy) { return NO_COPY; }
288
289         my $patron = _grab_user($session, $patron_id);
290
291         $copy->status( $copy->status->name );
292         $circ_objects->{copy} = $copy;
293
294         $patron->standing($patron_standings->{$patron->standing()});
295         $patron->profile( $patron_profiles->{$patron->profile});
296         $circ_objects->{patron} = $patron;
297         $circ_objects->{standings} = $patron_standings;
298
299         #$circ_objects->{title} = $title_req->gather(1);
300         $circ_objects->{title} = _grab_title_by_copy($session, $circ_objects->{copy}->id);
301         $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
302
303         $stash = Template::Stash->new(
304                         circ_objects                    => $circ_objects,
305                         result                                  => [],
306                         target_copy_status      => 1,
307                         );
308 }
309
310
311
312 sub run_script {
313
314         my $result;
315
316         my $template = Template->new(
317                 { 
318                         STASH                   => $stash,
319                         ABSOLUTE                => 1, 
320                         OUTPUT          => \$result,
321                 }
322         );
323
324         my $status = $template->process($circ_script);
325
326         if(!$status) { 
327                 throw OpenSRF::EX::ERROR 
328                         ("Error processing circ script " .  $template->error()); 
329         }
330
331         warn "Script result: $result\n";
332 }
333
334
335
336
337 __PACKAGE__->register_method(
338         method  => "permit_circ",
339         api_name        => "open-ils.circ.permit_checkout",
340 );
341
342 sub permit_circ {
343         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
344
345         my $copy_status_mangled;
346
347         my $renew = 0;
348         if(defined($outstanding_count) && $outstanding_count eq "renew") {
349                 $renew = 1;
350                 $outstanding_count = 0;
351         } else { $outstanding_count ||= 0; }
352
353         my $session     = OpenSRF::AppSession->create("open-ils.storage");
354         
355         # collect items necessary for circ calculation
356         my $status = gather_circ_objects( $session, $barcode, $user_id );
357
358         if( $status == NO_COPY ) {
359                 return { record => undef, 
360                         status => NO_COPY, 
361                         text => "No copy available with barcode $barcode"
362                 };
363         }
364         my $copy = $stash->get("circ_objects")->{copy};
365
366         if( $copy->status eq "8" ) { 
367                 $copy_status_mangled = 8; 
368                 $copy->status(0);
369         }
370
371
372         $stash->set("run_block", $permission_script);
373
374         # grab the number of copies checked out by the patron as
375         # well as the total fines
376         my $summary_req = $session->request(
377                 "open-ils.storage.action.circulation.patron_summary",
378                 $stash->get("circ_objects")->{patron}->id );
379         my $summary = $summary_req->gather(1);
380
381         $summary->[0] ||= 0;
382         $summary->[1] ||= 0.0;
383
384         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
385         $stash->set("patron_fines", $summary->[1] );
386         $stash->set("renew", 1) if $renew; 
387
388         # run the permissibility script
389         run_script();
390
391         my $arr = $stash->get("result");
392
393         if( $arr->[0] eq "0" and $copy_status_mangled == 8) {
394                 my $hold = $session->request(
395                         "open-ils.storage.direct.action.hold_request.search.current_copy",
396                         $copy->id )->gather(1);
397                 if($hold) {
398                         if( $hold->usr eq $user_id ) {
399                                 return { status => 0, text => "OK" };
400                         } else {
401                                 return { status => 6, 
402                                         text => "Copy is needed by a different user to fulfill a hold" };
403                         }
404                 }
405         }
406
407
408         
409         return { status => $arr->[0], text => $arr->[1] };
410
411 }
412
413
414
415 __PACKAGE__->register_method(
416         method  => "circulate",
417         api_name        => "open-ils.circ.checkout.barcode",
418 );
419
420 sub circulate {
421         my( $self, $client, $user_session, $barcode, $patron, $isrenew, $numrenews ) = @_;
422
423
424         my $session = $apputils->start_db_session();
425
426         gather_circ_objects( $session, $barcode, $patron );
427
428         # grab the copy statuses if we don't already have them
429         if(!$copy_statuses) {
430                 my $csreq = $session->request(
431                         "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
432                 $copy_statuses = $csreq->gather(1);
433         }
434
435         # put copy statuses into the stash
436         $stash->set("copy_statuses", $copy_statuses );
437
438         my $copy = $circ_objects->{copy};
439         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
440
441
442         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
443                 gmtime(OpenSRF::Utils->interval_to_seconds($circ->duration) + int(time()));
444         $year += 1900; $mon += 1;
445         my $due_date = sprintf(
446         '%s-%0.2d-%0.2dT%s:%0.2d:%0.s2-00',
447         $year, $mon, $mday, $hour, $min, $sec);
448
449         warn "Setting due date to $due_date\n";
450         $circ->due_date($due_date);
451
452         if($isrenew) {
453                 warn "Renewing circ.... ".$circ->id ." and setting num renews to " . $numrenews - 1 . "\n";
454                 $circ->renewal(1);
455                 $circ->clear_id;
456                 $circ->renewal_remaining($numrenews - 1);
457         }
458
459
460         # commit new circ object to db
461         my $commit = $session->request(
462                 "open-ils.storage.direct.action.circulation.create",
463                 $circ );
464         my $id = $commit->gather(1);
465
466         if(!$id) {
467                 throw OpenSRF::EX::ERROR 
468                         ("Error creating new circulation object");
469         }
470
471         # update the copy with the new circ
472         $copy->status( $stash->get("target_copy_status") );
473         $copy->location( $copy->location->id );
474         $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
475
476         # commit copy to db
477         my $copy_update = $session->request(
478                 "open-ils.storage.direct.asset.copy.update",
479                 $copy );
480         $copy_update->gather(1);
481
482         $apputils->commit_db_session($session);
483
484         # remove our circ object from the cache
485         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
486
487         # re-retrieve the the committed circ object  
488         $circ = $apputils->simple_scalar_request(
489                 "open-ils.storage",
490                 "open-ils.storage.direct.action.circulation.retrieve",
491                 $id );
492
493
494         # push the rules and due date into the circ object
495         $circ->duration_rule($duration);
496         $circ->max_fine_rule($max);
497         $circ->recuring_fine_rule($recurring);
498
499         # turn the biblio record into a friendly object
500         my $obj = $stash->get("circ_objects");
501         my $u = OpenILS::Utils::ModsParser->new();
502         $u->start_mods_batch( $circ_objects->{title}->marc() );
503         my $mods = $u->finish_mods_batch();
504
505
506         return { circ => $circ, copy => $copy, record => $mods };
507
508 }
509
510
511
512 # runs the duration, recurring_fines, and max_fines scripts.
513 # builds the new circ object based on the rules returned from 
514 # these scripts. 
515 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
516 sub run_circ_scripts {
517         my $session = shift;
518
519         # go through all of the scripts and process
520         # each script returns 
521         # [ rule_name, level (appropriate to the script) ]
522         $stash->set("result", [] );
523         $stash->set("run_block", $duration_script);
524         run_script();
525         my $duration_rule = $stash->get("result");
526
527         $stash->set("run_block", $recurring_fines_script);
528         $stash->set("result", [] );
529         run_script();
530         my $rec_fines_rule = $stash->get("result");
531
532         $stash->set("run_block", $max_fines_script);
533         $stash->set("result", [] );
534         run_script();
535         my $max_fines_rule = $stash->get("result");
536
537         my $obj = $stash->get("circ_objects");
538
539         # ----------------------------------------------------------
540         # find the rules objects based on the rule names returned from
541         # the various scripts.
542         my $dur_req = $session->request(
543                 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
544                 $duration_rule->[0] );
545
546         my $rec_req = $session->request(
547                 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
548                 $rec_fines_rule->[0] );
549
550         my $max_req = $session->request(
551                 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
552                 $max_fines_rule->[0] );
553
554         my $duration    = $dur_req->gather(1)->[0];
555         my $recurring   = $rec_req->gather(1)->[0];
556         my $max                 = $max_req->gather(1)->[0];
557
558         my $copy = $circ_objects->{copy};
559
560         use Data::Dumper;
561         warn "Building a new circulation object with\n".
562                 "=> copy "                              . Dumper($copy) .
563                 "=> duration_rule "     . Dumper($duration_rule) .
564                 "=> rec_files_rule " . Dumper($rec_fines_rule) .
565                 "=> duration "                  . Dumper($duration) .
566                 "=> recurring "         . Dumper($recurring) .
567                 "=> max "                               . Dumper($max);
568
569
570         # build the new circ object
571         my $circ =  build_circ_object($session, $copy, $duration_rule->[1], 
572                         $rec_fines_rule->[1], $duration, $recurring, $max );
573
574         return ($circ, $duration, $recurring, $max);
575
576 }
577
578 # ------------------------------------------------------------------
579 # Builds a new circ object
580 # ------------------------------------------------------------------
581 sub build_circ_object {
582         my( $session, $copy, $dur_level, $rec_level, 
583                         $duration, $recurring, $max ) = @_;
584
585         my $circ = new Fieldmapper::action::circulation;
586
587         $circ->circ_lib( $copy->circ_lib->id() );
588         if($dur_level == 1) {
589                 $circ->duration( $duration->shrt );
590         } elsif($dur_level == 2) {
591                 $circ->duration( $duration->normal );
592         } elsif($dur_level == 3) {
593                 $circ->duration( $duration->extended );
594         }
595
596         if($rec_level == 1) {
597                 $circ->recuring_fine( $recurring->low );
598         } elsif($rec_level == 2) {
599                 $circ->recuring_fine( $recurring->normal );
600         } elsif($rec_level == 3) {
601                 $circ->recuring_fine( $recurring->high );
602         }
603
604         $circ->duration_rule( $duration->name );
605         $circ->recuring_fine_rule( $recurring->name );
606         $circ->max_fine_rule( $max->name );
607         $circ->max_fine( $max->amount );
608
609         $circ->fine_interval($recurring->recurance_interval);
610         $circ->renewal_remaining( $duration->max_renewals );
611         $circ->target_copy( $copy->id );
612         $circ->usr( $circ_objects->{patron}->id );
613
614         return $circ;
615
616 }
617
618 __PACKAGE__->register_method(
619         method  => "transit_receive",
620         api_name        => "open-ils.circ.transit.receive",
621         notes           => <<"  NOTES");
622         Receives a copy that is in transit.  
623         Params are login_session and copyid.
624         Logged in user must have COPY_CHECKIN priveleges.
625
626         status 3 means that this transit is destined for somewhere else
627         status 10 means the copy is not in transit
628         status 11 means the transit is complete, does not need processing
629         status 12 means copy is in transit but no tansit was found
630
631         NOTES
632
633 sub transit_receive {
634         my( $self, $client, $login_session, $copyid ) = @_;
635
636         my $user = $apputils->check_user_session($login_session);
637
638         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
639                 return OpenILS::Perm->new("COPY_CHECKIN");
640         }
641
642         my $session = $apputils->start_db_session();
643         my $copy = _grab_copy_by_id($session, $copyid);
644         my $transit;
645
646         if(!$copy->status eq "6") {
647                 return { status => 10, route_to => $copy->circ_lib };
648         }
649
650
651         $transit = $session->request(
652                 "open-ils.storage.direct.action.transit_copy.search_where",
653                 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
654
655         if($transit) {
656
657                 if( defined($transit->dest_recv_time) ) {
658                         return { status => 11, route_to => $copy->circ_lib };
659                 }
660
661                 if($transit->dest ne $user->home_ou) {
662                         return { status => 3, route_to => $transit->dest };
663                 }
664
665                 $transit->dest_recv_time("now");
666                 my $s = $session->request(
667                         "open-ils.storage.direct.action.transit_copy.update",
668                         $transit );
669
670                 my $holdtransit = $session->request(
671                         "open-ils.storage.direct.action.hold_transit_copy.retrieve",
672                         $transit->id );
673
674                 if($holdtransit) {
675
676                         my $hold = $session->request(
677                                 "open-ils.storage.direct.action.hold_request.retrieve",
678                                 $holdtransit->hold )->gather(1);
679                         if(!$hold) {
680                                 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
681                         }
682
683                         # put copy on the holds shelf
684                         $copy->status(8); #hold shelf status
685                         $copy->editor($user->id); #hold shelf status
686                         $copy->edit_date("now"); #hold shelf status
687
688                         my $s = $session->request(
689                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
690                         if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
691
692                         return { status => 0, route_to => $hold->pickup_lib };
693                 }
694
695         } else { 
696                 return { status => 12, route_to => $copy->circ_lib };
697         } 
698
699 }
700
701
702
703 __PACKAGE__->register_method(
704         method  => "checkin",
705         api_name        => "open-ils.circ.checkin.barcode",
706         notes           => <<"  NOTES");
707         Checks in based on barcode
708         Returns record, status, text, circ, copy, route_to 
709         'status' values:
710                 0 = OK
711                 1 = 'copy required to fulfil a hold'
712         NOTES
713
714 sub checkin {
715         my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
716
717         my $err;
718         my $copy;
719         my $circ;
720
721         my $transaction;
722         my $user = $apputils->check_user_session($user_session);
723
724         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
725                 return OpenILS::Perm->new("COPY_CHECKIN");
726         }
727
728         my $session = $apputils->start_db_session();
729
730
731
732         try {
733                         
734                 warn "retrieving copy for checkin\n";
735
736                         
737                 my $copy_req = $session->request(
738                         "open-ils.storage.direct.asset.copy.search.barcode.atomic", 
739                         $barcode );
740                 $copy = $copy_req->gather(1)->[0];
741                 if(!$copy) {
742                         $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
743                 }
744
745                 if($copy->status eq "6") { #copy is in transit, deal with it
746                         my $method = $self->method_lookup("open-ils.circ.transit.receive");
747                         return $method->run( $user_session, $copy->id );
748                 }
749
750
751                 if(!$shelving_locations) {
752                         my $sh_req = $session->request(
753                                 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
754                         $shelving_locations = $sh_req->gather(1);
755                         $shelving_locations = 
756                                 { map { (''.$_->id => $_->name) } @$shelving_locations };
757                 }
758
759                 
760                 $copy->status(0);
761         
762                 # find circ's where the transaction is still open for the
763                 # given copy.  should only be one.
764                 warn "Retrieving circ for checkin\n";
765                 my $circ_req = $session->request(
766                         "open-ils.storage.direct.action.circulation.search.atomic",
767                         { target_copy => $copy->id, xact_finish => undef } );
768         
769                 $circ = $circ_req->gather(1)->[0];
770
771         
772                 if(!$circ) {
773                         $err = "No circulation exists for the given barcode";
774
775                 } else {
776
777                         $transaction = $session->request(
778                                 "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
779         
780                         warn "Checking in circ ". $circ->id . "\n";
781                 
782                         $circ->stop_fines("CHECKIN");
783                         $circ->stop_fines("RENEW") if($isrenewal);
784                         $circ->xact_finish("now") if($transaction->balance_owed <= 0 );
785                 
786                         my $cp_up = $session->request(
787                                 "open-ils.storage.direct.asset.copy.update", $copy );
788                         $cp_up->gather(1);
789                 
790                         my $ci_up = $session->request(
791                                 "open-ils.storage.direct.action.circulation.update",
792                                 $circ );
793                         $ci_up->gather(1);
794                 
795                 
796                         warn "Checkin succeeded\n";
797                 }
798         
799         } catch Error with {
800                 my $e = shift;
801                 $err = "Error checking in: $e";
802         };
803         
804         if($err) {
805
806                 return { record => undef, status => -1, text => $err };
807
808         } else {
809
810                 my $status = "0";
811                 my $status_text = "OK";
812
813                 # see if this copy can fulfill a hold
814                 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
815
816                 my $route_to = $shelving_locations->{$copy->location};
817
818                 if($hold) { 
819                         $status = "1";
820                         $status_text = "Copy needed to fulfill hold";
821                         $route_to = $hold->pickup_lib;
822                 }
823         
824                 $apputils->commit_db_session($session);
825
826                 my $record = $apputils->simple_scalar_request(
827                         "open-ils.storage",
828                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
829                         $copy->id() );
830
831                 my $u = OpenILS::Utils::ModsParser->new();
832                 $u->start_mods_batch( $record->marc() );
833                 my $mods = $u->finish_mods_batch();
834
835                 return { 
836                         record => $mods, 
837                         status => $status,
838                         text => $status_text,
839                         circ => $circ,
840                         copy => $copy,
841                         route_to => $route_to,
842                 };
843         }
844
845         return 1;
846
847 }
848
849
850
851
852
853 # ------------------------------------------------------------------------------
854 # RENEWALS
855 # ------------------------------------------------------------------------------
856
857
858 __PACKAGE__->register_method(
859         method  => "renew",
860         api_name        => "open-ils.circ.renew",
861         notes           => <<"  NOTES");
862         open-ils.circ.renew(login_session, circ_object);
863         Renews the provided circulation.  login_session is the requestor of the
864         renewal and if the logged in user is not the same as circ->usr, then
865         the logged in user must have RENEW_CIRC permissions.
866         NOTES
867
868 sub renew {
869         my($self, $client, $login_session, $circ) = @_;
870
871         throw OpenSRF::EX::InvalidArg 
872                 ("open-ils.circ.renew no circ") unless defined($circ);
873
874         my $user = $apputils->check_user_session($login_session);
875
876         my $session = OpenSRF::AppSession->create("open-ils.storage");
877         my $copy = _grab_copy_by_id($session, $circ->target_copy);
878
879         my $r = $session->request(
880                 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
881                 $copy->id )->gather(1);
882
883         my @holdids = map { $_->hold  } @$r;
884
885         if(@$r != 0) { 
886
887                 my $holds = $session->request(
888                         "open-ils.storage.direct.action.hold_request.search_where", 
889                                 { id => \@holdids, current_copy => undef } )->gather(1);
890
891                 if( $holds and $user->id ne $circ->usr ) {
892                         if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
893                                 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
894                         }
895                 }
896
897                 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex; 
898         }
899
900
901         if(!ref($circ)) {
902                 $circ = $session->request(
903                         "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
904         }
905
906         my $iid = $circ->id;
907         warn "Attempting to renew circ " . $iid . "\n";
908
909         if($user->id ne $circ->usr) {
910                 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
911                         return OpenILS::Perm->new("RENEW_CIRC");
912                 }
913         }
914
915         if($circ->renewal_remaining <= 0) {
916                 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
917
918
919
920         # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
921         # XXX check overdue..?
922
923         my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
924         my ($status) = $checkin->run($login_session, $copy->barcode, 1);
925         return $status if ($status->{status} ne "0"); 
926         warn "Renewal checkin completed for $iid\n";
927
928         my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
929         ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
930         return $status if($status->{status} ne "0");
931         warn "Renewal permit checkout completed for $iid\n";
932
933         my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
934         ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
935         warn "Renewal checkout completed for $iid\n";
936         return $status;
937
938 }
939
940
941
942 1;