]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
more holds capturing, transits, etc.
[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         NOTES
623
624 # status 3 means that this transit is destined for somewhere else
625 sub transit_receive {
626         my( $self, $client, $login_session, $copyid ) = @_;
627
628         my $user = $apputils->check_user_session($login_session);
629
630         my $session = $apputils->start_db_session();
631         my $copy = _grab_copy_by_id($session, $copyid);
632         my $transit;
633
634         if(!$copy->status eq "6") {
635                 throw OpenSRF::EX::ERROR ("Copy is not in transit");
636         }
637
638         $transit = $session->request(
639                 "open-ils.storage.direct.action.transit_copy.search_where",
640                 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
641
642         if($transit) {
643
644                 if($transit->dest ne $user->home_ou) {
645                         return { status => 3, route_to => $transit->dest };
646                 }
647
648                 $transit->dest_recv_time("now");
649                 my $s = $session->request(
650                         "open-ils.storage.direct.action.transit_copy.update",
651                         $transit );
652
653                 my $holdtransit = $session->request(
654                         "open-ils.storage.direct.action.hold_transit_copy.retrieve",
655                         $transit->id );
656
657                 if($holdtransit) {
658
659                         my $hold = $session->request(
660                                 "open-ils.storage.direct.action.hold_request.retrieve",
661                                 $holdtransit->hold )->gather(1);
662                         $copy->status(8); #hold shelf status
663
664                         my $s = $session->request(
665                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
666                         if(!$s) {} # blah..
667
668                         return { status => 0, route_to => $hold->pickup_lib };
669                 }
670
671         } else { } #message...
672
673 }
674
675
676
677 __PACKAGE__->register_method(
678         method  => "checkin",
679         api_name        => "open-ils.circ.checkin.barcode",
680         notes           => <<"  NOTES");
681         Checks in based on barcode
682         Returns record, status, text, circ, copy, route_to 
683         'status' values:
684                 0 = OK
685                 1 = 'copy required to fulfil a hold'
686         NOTES
687
688 sub checkin {
689         my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
690
691         my $err;
692         my $copy;
693         my $circ;
694
695         my $transaction;
696         my $user = $apputils->check_user_session($user_session);
697
698         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
699                 return OpenILS::Perm->new("COPY_CHECKIN");
700         }
701
702         my $session = $apputils->start_db_session();
703
704
705
706         try {
707                         
708                 warn "retrieving copy for checkin\n";
709
710                         
711                 my $copy_req = $session->request(
712                         "open-ils.storage.direct.asset.copy.search.barcode.atomic", 
713                         $barcode );
714                 $copy = $copy_req->gather(1)->[0];
715                 if(!$copy) {
716                         $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
717                 }
718
719                 if($copy->status eq "6") { #copy is in transit, deal with it
720                         my $method = $self->method_lookup("open-ils.circ.transit.receive");
721                         return $method->run( $user_session, $copy->id );
722                 }
723
724
725                 if(!$shelving_locations) {
726                         my $sh_req = $session->request(
727                                 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
728                         $shelving_locations = $sh_req->gather(1);
729                         $shelving_locations = 
730                                 { map { (''.$_->id => $_->name) } @$shelving_locations };
731                 }
732
733                 
734                 $copy->status(0);
735         
736                 # find circ's where the transaction is still open for the
737                 # given copy.  should only be one.
738                 warn "Retrieving circ for checkin\n";
739                 my $circ_req = $session->request(
740                         "open-ils.storage.direct.action.circulation.search.atomic",
741                         { target_copy => $copy->id, xact_finish => undef } );
742         
743                 $circ = $circ_req->gather(1)->[0];
744
745         
746                 if(!$circ) {
747                         $err = "No circulation exists for the given barcode";
748
749                 } else {
750
751                         $transaction = $session->request(
752                                 "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
753         
754                         warn "Checking in circ ". $circ->id . "\n";
755                 
756                         $circ->stop_fines("CHECKIN");
757                         $circ->stop_fines("RENEW") if($isrenewal);
758                         $circ->xact_finish("now") if($transaction->balance_owed <= 0 );
759                 
760                         my $cp_up = $session->request(
761                                 "open-ils.storage.direct.asset.copy.update", $copy );
762                         $cp_up->gather(1);
763                 
764                         my $ci_up = $session->request(
765                                 "open-ils.storage.direct.action.circulation.update",
766                                 $circ );
767                         $ci_up->gather(1);
768                 
769                 
770                         warn "Checkin succeeded\n";
771                 }
772         
773         } catch Error with {
774                 my $e = shift;
775                 $err = "Error checking in: $e";
776         };
777         
778         if($err) {
779
780                 return { record => undef, status => -1, text => $err };
781
782         } else {
783
784                 my $status = "0";
785                 my $status_text = "OK";
786
787                 # see if this copy can fulfill a hold
788                 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
789
790                 my $route_to = $shelving_locations->{$copy->location} 
791
792                 if($hold) { 
793                         $status = "1";
794                         $status_text = "Copy needed to fulfill hold";
795                         $route_to = $hold->pickup_lib;
796                 }
797         
798                 $apputils->commit_db_session($session);
799
800                 my $record = $apputils->simple_scalar_request(
801                         "open-ils.storage",
802                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
803                         $copy->id() );
804
805                 my $u = OpenILS::Utils::ModsParser->new();
806                 $u->start_mods_batch( $record->marc() );
807                 my $mods = $u->finish_mods_batch();
808
809                 return { 
810                         record => $mods, 
811                         status => $status,
812                         text => $status_text,
813                         circ => $circ,
814                         copy => $copy,
815                         route_to => $routet_to,
816                 };
817         }
818
819         return 1;
820
821 }
822
823
824
825
826
827 # ------------------------------------------------------------------------------
828 # RENEWALS
829 # ------------------------------------------------------------------------------
830
831
832 __PACKAGE__->register_method(
833         method  => "renew",
834         api_name        => "open-ils.circ.renew",
835         notes           => <<"  NOTES");
836         open-ils.circ.renew(login_session, circ_object);
837         Renews the provided circulation.  login_session is the requestor of the
838         renewal and if the logged in user is not the same as circ->usr, then
839         the logged in user must have RENEW_CIRC permissions.
840         NOTES
841
842 sub renew {
843         my($self, $client, $login_session, $circ) = @_;
844
845         throw OpenSRF::EX::InvalidArg 
846                 ("open-ils.circ.renew no circ") unless defined($circ);
847
848         my $user = $apputils->check_user_session($login_session);
849
850         my $session = OpenSRF::AppSession->create("open-ils.storage");
851         my $copy = _grab_copy_by_id($session, $circ->target_copy);
852
853         my $r = $session->request(
854                 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
855                 $copy->id )->gather(1);
856
857         my @holdids = map { $_->hold  } @$r;
858
859         if(@$r != 0) { 
860
861                 my $holds = $session->request(
862                         "open-ils.storage.direct.action.hold_request.search_where", 
863                                 { id => \@holdids, current_copy => undef } )->gather(1);
864
865                 if( $holds and $user->id ne $circ->usr ) {
866                         if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
867                                 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
868                         }
869                 }
870
871                 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex; 
872         }
873
874
875         if(!ref($circ)) {
876                 $circ = $session->request(
877                         "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
878         }
879
880         my $iid = $circ->id;
881         warn "Attempting to renew circ " . $iid . "\n";
882
883         if($user->id ne $circ->usr) {
884                 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
885                         return OpenILS::Perm->new("RENEW_CIRC");
886                 }
887         }
888
889         if($circ->renewal_remaining <= 0) {
890                 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
891
892
893
894         # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
895         # XXX check overdue..?
896
897         my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
898         my ($status) = $checkin->run($login_session, $copy->barcode, 1);
899         return $status if ($status->{status} ne "0"); 
900         warn "Renewal checkin completed for $iid\n";
901
902         my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
903         ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
904         return $status if($status->{status} ne "0");
905         warn "Renewal permit checkout completed for $iid\n";
906
907         my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
908         ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
909         warn "Renewal checkout completed for $iid\n";
910         return $status;
911
912 }
913
914
915
916 1;