changed renewal to opac_renewal for now
[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", $barcode );
165         return $copy_req->gather(1);
166 }
167
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", 
173                 $id );
174         my $c = $copy_req->gather(1);
175         if($c) { return _grab_copy_by_barcode($session, $c->barcode); }
176         return undef;
177 }
178
179
180 sub gather_hold_objects {
181         my($session, $hold, $copy, $args) = @_;
182
183         _grab_patron_standings($session);
184         _grab_patron_profiles($session);
185
186
187         # flesh me
188         $copy = _grab_copy_by_barcode($session, $copy->barcode) unless ref($copy->circ_lib);
189
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);
197
198         $copy->status( $copy->status->name );
199         $patron->standing($patron_standings->{$patron->standing()});
200         $patron->profile( $patron_profiles->{$patron->profile});
201
202         $hold_objects->{patron}         = $patron;
203
204         return $hold_objects;
205 }
206
207
208
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
214         NOTES
215
216 sub permit_hold {
217         my( $self, $client, $hold, $copy, $args ) = @_;
218
219         my $session     = OpenSRF::AppSession->create("open-ils.storage");
220         
221         # collect items necessary for circ calculation
222         my $hold_objects = gather_hold_objects( $session, $hold, $copy, $args );
223
224         $stash = Template::Stash->new(
225                         circ_objects                    => $hold_objects,
226                         result                                  => []);
227
228         $stash->set("run_block", $permit_hold_script);
229
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);
233         $summary->[0] ||= 0;
234         $summary->[1] ||= 0.0;
235
236         $stash->set("patron_copies", $summary->[0] );
237         $stash->set("patron_fines", $summary->[1] );
238
239         # run the permissibility script
240         run_script();
241         my $result = $stash->get("result");
242
243         # 0 means OK in the script
244         return 1 if($result->[0] == 0);
245         return 0;
246
247 }
248
249
250
251
252
253 # ----------------------------------------------------------------
254 # Collect all of the objects necessary for calculating the
255 # circ matrix.
256 # ----------------------------------------------------------------
257 sub gather_circ_objects {
258         my( $session, $barcode_string, $patron_id, $copy ) = @_;
259
260         throw OpenSRF::EX::ERROR 
261                 ("gather_circ_objects needs data")
262                         unless ($barcode_string and $patron_id);
263
264         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
265
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);
269
270 #       if($circ_objects) { 
271 #               $stash = Template::Stash->new(
272 #                       circ_objects                    => $circ_objects,
273 #                       result                                  => [],
274 #                       target_copy_status      => 1,
275 #                       );
276 #               return;
277 #       }
278
279         # only necessary if the circ objects have not been built yet
280
281         _grab_patron_standings($session);
282         _grab_patron_profiles($session);
283
284
285         if(!$copy) {
286                 $copy = _grab_copy_by_barcode($session, $barcode_string);
287                 if(!$copy) { return NO_COPY; }
288         }
289
290         my $patron = _grab_user($session, $patron_id);
291
292         $copy->status( $copy->status->name );
293         $circ_objects->{copy} = $copy;
294
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;
299
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 );
303
304         $stash = Template::Stash->new(
305                         circ_objects                    => $circ_objects,
306                         result                                  => [],
307                         target_copy_status      => 1,
308                         );
309 }
310
311
312
313 sub run_script {
314
315         my $result;
316
317         my $template = Template->new(
318                 { 
319                         STASH                   => $stash,
320                         ABSOLUTE                => 1, 
321                         OUTPUT          => \$result,
322                 }
323         );
324
325         my $status = $template->process($circ_script);
326
327         if(!$status) { 
328                 throw OpenSRF::EX::ERROR 
329                         ("Error processing circ script " .  $template->error()); 
330         }
331
332         warn "Script result: $result\n";
333 }
334
335
336
337
338 __PACKAGE__->register_method(
339         method  => "permit_circ",
340         api_name        => "open-ils.circ.permit_checkout",
341 );
342
343 sub permit_circ {
344         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
345
346         my $copy_status_mangled;
347         my $hold;
348
349         my $renew = 0;
350         if(defined($outstanding_count) && $outstanding_count eq "renew") {
351                 $renew = 1;
352                 $outstanding_count = 0;
353         } else { $outstanding_count ||= 0; }
354
355         my $session     = OpenSRF::AppSession->create("open-ils.storage");
356         
357         # collect items necessary for circ calculation
358         my $status = gather_circ_objects( $session, $barcode, $user_id );
359
360         if( $status == NO_COPY ) {
361                 return { record => undef, 
362                         status => NO_COPY, 
363                         text => "No copy available with barcode $barcode"
364                 };
365         }
366
367         my $copy = $stash->get("circ_objects")->{copy};
368
369         warn "Found copy in permit: " . $copy->id . "\n";
370
371         warn "Copy status in checkout is " . $stash->get("circ_objects")->{copy}->status . "\n";
372
373         $stash->set("run_block", $permission_script);
374
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 );
380
381         my $summary = $summary_req->gather(1);
382
383         $summary->[0] ||= 0;
384         $summary->[1] ||= 0.0;
385
386         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
387         $stash->set("patron_fines", $summary->[1] );
388         $stash->set("renew", 1) if $renew; 
389
390         # run the permissibility script
391         run_script();
392
393         my $arr = $stash->get("result");
394
395         if( $arr->[0]  eq "0" ) { # and $copy_status_mangled == 8) {
396
397                 # see if this copy is needed to fulfil a hold
398
399                 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
400
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);
404
405                 $hold = $hold->[0];
406
407                 if($hold) { warn "Found hold in circ permit with id " . $hold->id . "\n"; }
408
409                 if($hold) {
410
411                         warn "Found unfulfilled hold in permit ". $hold->id . "\n";
412
413                         if( $hold->usr eq $user_id ) {
414                                 return { status => 0, text => "OK" };
415
416                         } else {
417                                 return { status => 6, 
418                                         text => "Copy is needed by a different user to fulfill a hold" };
419                         }
420                 } 
421         }
422         
423         return { status => $arr->[0], text => $arr->[1] };
424
425 }
426
427
428
429 __PACKAGE__->register_method(
430         method  => "circulate",
431         api_name        => "open-ils.circ.checkout.barcode",
432 );
433
434 sub circulate {
435         my( $self, $client, $user_session, $barcode, $patron, $isrenew, $numrenews ) = @_;
436
437
438         my $user = $apputils->check_user_session($user_session);
439         my $session = $apputils->start_db_session();
440
441         my $copy =  _grab_copy_by_barcode($session, $barcode);
442         my $realstatus = $copy->status->id;
443
444         warn "Checkout copy status is $realstatus\n";
445
446         gather_circ_objects( $session, $barcode, $patron, $copy );
447
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);
453         }
454
455         # put copy statuses into the stash
456         $stash->set("copy_statuses", $copy_statuses );
457
458         $copy = $circ_objects->{copy};
459         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
460
461
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);
468
469         warn "Setting due date to $due_date\n";
470         $circ->due_date($due_date);
471         $circ->circ_lib($user->home_ou);
472
473         if($isrenew) {
474                 warn "Renewing circ.... ".$circ->id ." and setting num renews to " . $numrenews - 1 . "\n";
475                 $circ->opac_renewal(1); # XXX different for different types ! 
476                 $circ->clear_id;
477                 $circ->renewal_remaining($numrenews - 1);
478         }
479
480
481         # commit new circ object to db
482         my $commit = $session->request(
483                 "open-ils.storage.direct.action.circulation.create", $circ );
484         my $id = $commit->gather(1);
485
486         if(!$id) {
487                 throw OpenSRF::EX::ERROR 
488                         ("Error creating new circulation object");
489         }
490
491         # update the copy with the new circ
492         $copy->status( $stash->get("target_copy_status") );
493         $copy->location( $copy->location->id );
494         $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
495
496         # commit copy to db
497         my $copy_update = $session->request(
498                 "open-ils.storage.direct.asset.copy.update",
499                 $copy );
500         $copy_update->gather(1);
501
502
503         if( $realstatus eq "8" ) { # on holds shelf
504
505
506                 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
507
508                 my $hold = $session->request(
509                         "open-ils.storage.direct.action.hold_request.search.atomic",
510                          current_copy =>  $copy->id , fulfillment_time => undef )->gather(1);
511                 $hold = $hold->[0];
512
513                 if($hold) {
514
515                         $hold->fulfillment_time("now");
516                                 my $s = $session->request(
517                                         "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
518                                 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
519                 }
520         }
521
522         $apputils->commit_db_session($session);
523
524         # remove our circ object from the cache
525         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
526
527         # re-retrieve the the committed circ object  
528         $circ = $apputils->simple_scalar_request(
529                 "open-ils.storage",
530                 "open-ils.storage.direct.action.circulation.retrieve",
531                 $id );
532
533
534         # push the rules and due date into the circ object
535         $circ->duration_rule($duration);
536         $circ->max_fine_rule($max);
537         $circ->recuring_fine_rule($recurring);
538
539         # turn the biblio record into a friendly object
540         my $obj = $stash->get("circ_objects");
541         my $u = OpenILS::Utils::ModsParser->new();
542         $u->start_mods_batch( $circ_objects->{title}->marc() );
543         my $mods = $u->finish_mods_batch();
544
545
546         return { circ => $circ, copy => $copy, record => $mods };
547
548 }
549
550
551
552 # runs the duration, recurring_fines, and max_fines scripts.
553 # builds the new circ object based on the rules returned from 
554 # these scripts. 
555 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
556 sub run_circ_scripts {
557         my $session = shift;
558
559         # go through all of the scripts and process
560         # each script returns 
561         # [ rule_name, level (appropriate to the script) ]
562         $stash->set("result", [] );
563         $stash->set("run_block", $duration_script);
564         run_script();
565         my $duration_rule = $stash->get("result");
566
567         $stash->set("run_block", $recurring_fines_script);
568         $stash->set("result", [] );
569         run_script();
570         my $rec_fines_rule = $stash->get("result");
571
572         $stash->set("run_block", $max_fines_script);
573         $stash->set("result", [] );
574         run_script();
575         my $max_fines_rule = $stash->get("result");
576
577         my $obj = $stash->get("circ_objects");
578
579         # ----------------------------------------------------------
580         # find the rules objects based on the rule names returned from
581         # the various scripts.
582         my $dur_req = $session->request(
583                 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
584                 $duration_rule->[0] );
585
586         my $rec_req = $session->request(
587                 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
588                 $rec_fines_rule->[0] );
589
590         my $max_req = $session->request(
591                 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
592                 $max_fines_rule->[0] );
593
594         my $duration    = $dur_req->gather(1)->[0];
595         my $recurring   = $rec_req->gather(1)->[0];
596         my $max                 = $max_req->gather(1)->[0];
597
598         my $copy = $circ_objects->{copy};
599
600         use Data::Dumper;
601         warn "Building a new circulation object with\n".
602                 "=> copy "                              . Dumper($copy) .
603                 "=> duration_rule "     . Dumper($duration_rule) .
604                 "=> rec_files_rule " . Dumper($rec_fines_rule) .
605                 "=> duration "                  . Dumper($duration) .
606                 "=> recurring "         . Dumper($recurring) .
607                 "=> max "                               . Dumper($max);
608
609
610         # build the new circ object
611         my $circ =  build_circ_object($session, $copy, $duration_rule->[1], 
612                         $rec_fines_rule->[1], $duration, $recurring, $max );
613
614         return ($circ, $duration, $recurring, $max);
615
616 }
617
618 # ------------------------------------------------------------------
619 # Builds a new circ object
620 # ------------------------------------------------------------------
621 sub build_circ_object {
622         my( $session, $copy, $dur_level, $rec_level, 
623                         $duration, $recurring, $max ) = @_;
624
625         my $circ = new Fieldmapper::action::circulation;
626
627         $circ->circ_lib( $copy->circ_lib->id() );
628         if($dur_level == 1) {
629                 $circ->duration( $duration->shrt );
630         } elsif($dur_level == 2) {
631                 $circ->duration( $duration->normal );
632         } elsif($dur_level == 3) {
633                 $circ->duration( $duration->extended );
634         }
635
636         if($rec_level == 1) {
637                 $circ->recuring_fine( $recurring->low );
638         } elsif($rec_level == 2) {
639                 $circ->recuring_fine( $recurring->normal );
640         } elsif($rec_level == 3) {
641                 $circ->recuring_fine( $recurring->high );
642         }
643
644         $circ->duration_rule( $duration->name );
645         $circ->recuring_fine_rule( $recurring->name );
646         $circ->max_fine_rule( $max->name );
647         $circ->max_fine( $max->amount );
648
649         $circ->fine_interval($recurring->recurance_interval);
650         $circ->renewal_remaining( $duration->max_renewals );
651         $circ->target_copy( $copy->id );
652         $circ->usr( $circ_objects->{patron}->id );
653
654         return $circ;
655
656 }
657
658 __PACKAGE__->register_method(
659         method  => "transit_receive",
660         api_name        => "open-ils.circ.transit.receive",
661         notes           => <<"  NOTES");
662         Receives a copy that is in transit.  
663         Params are login_session and copyid.
664         Logged in user must have COPY_CHECKIN priveleges.
665
666         status 3 means that this transit is destined for somewhere else
667         status 10 means the copy is not in transit
668         status 11 means the transit is complete, does not need processing
669         status 12 means copy is in transit but no tansit was found
670
671         NOTES
672
673 sub transit_receive {
674         my( $self, $client, $login_session, $copyid ) = @_;
675
676         my $user = $apputils->check_user_session($login_session);
677
678         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
679                 return OpenILS::Perm->new("COPY_CHECKIN");
680         }
681
682         warn "Receiving copy for transit $copyid\n";
683
684         my $session = $apputils->start_db_session();
685         my $copy = $session->request(
686                         "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
687         my $transit;
688
689         if(!$copy->status eq "6") {
690                 return { status => 10, route_to => $copy->circ_lib };
691         }
692
693
694         $transit = $session->request(
695                 "open-ils.storage.direct.action.transit_copy.search_where",
696                 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
697
698
699         my $record = _grab_title_by_copy($session, $copy->id);
700         my $u = OpenILS::Utils::ModsParser->new();
701         $u->start_mods_batch( $record->marc() );
702         $record = $u->finish_mods_batch();
703
704         if($transit) {
705
706                 warn "Found transit " . $transit->id . " for copy $copyid\n";
707
708                 if( defined($transit->dest_recv_time) ) {
709                         return { status => 11, route_to => $copy->circ_lib, 
710                                 text => "Transit is already complete for this copy" };
711                 }
712
713                 $transit->dest_recv_time("now");
714                 my $s = $session->request(
715                         "open-ils.storage.direct.action.transit_copy.update", $transit )->gather(1);
716
717                 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating transit " . $transit->id . "\n"); }
718
719                 warn "Searching for hold transit with id " . $transit->id . "\n";
720
721                 my $holdtransit = $session->request(
722                         "open-ils.storage.direct.action.hold_transit_copy.retrieve",
723                         $transit->id )->gather(1);
724
725                 if($holdtransit) {
726
727                         warn "Found hold transit for copy $copyid\n";
728
729                         my $hold = $session->request(
730                                 "open-ils.storage.direct.action.hold_request.retrieve",
731                                 $holdtransit->hold )->gather(1);
732
733                         if(!$hold) {
734                                 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
735                         }
736
737                         # put copy on the holds shelf
738                         $copy->status(8); #hold shelf status
739                         $copy->editor($user->id); #hold shelf status
740                         $copy->edit_date("now"); #hold shelf status
741
742                         warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
743
744                         my $s = $session->request(
745                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
746                         if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
747
748                         $apputils->commit_db_session($session);
749
750                         return { status => 4, route_to => "Holds Shelf", 
751                                 text => "Transit Complete", record => $record, copy => $copy  };
752
753
754                 } else {
755
756                         if($transit->dest eq $user->home_ou) {
757
758                                 $copy->status(0); 
759                                 $copy->editor($user->id); 
760                                 $copy->edit_date("now"); 
761
762                                 my $s = $session->request(
763                                         "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
764                                 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
765
766                                 my($status, $status_text) = (0, "Transit Complete");
767
768                                 my $circ;
769
770                                 if($transit->copy_status eq "3") { #if copy is lost
771                                         $status = 2;
772                                         $status_text = "Copy is marked as LOST";
773
774                                         $circ = $session->request(
775                                                 "open-ils.storage.direct.action.circulation.search_where",
776                                                 { target_copy => $copy->id, xact_finish => undef }, 
777                                                 { order_by => "xact_start desc", limit => 1 } )->gather(1);
778
779
780                                         if($circ) {
781
782                                                 my $transaction = $session->request(
783                                                         "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
784
785                                                 $circ->xact_finish("now") if($transaction->balance_owed <= 0);
786
787                                                 my $s = $session->request(
788                                                         "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
789
790                                                 if(!$s) {
791                                                         throw OpenSRF::EX::ERROR ("Error updating circulation " . $circ->id);
792                                                 }
793                                         }
794
795                                 }
796
797                                 $apputils->commit_db_session($session);
798
799                                 return { 
800                                         status => $status, 
801                                         route_to => $user->home_ou, 
802                                         text => $status_text, 
803                                         record => $record, 
804                                         circ    => $circ,
805                                         copy => $copy  };
806
807                         } else {
808
809                                 $apputils->rollback_db_session($session);
810
811
812                                 return { 
813                                         copy => $copy, record => $record, 
814                                         status => 3, route_to => $transit->dest, 
815                                         text => "Transit needs to be routed" };
816
817                         }
818
819                 }
820
821
822         } else { 
823
824                 $apputils->rollback_db_session($session);
825                 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
826         } 
827
828 }
829
830
831
832 __PACKAGE__->register_method(
833         method  => "checkin",
834         api_name        => "open-ils.circ.checkin.barcode",
835         notes           => <<"  NOTES");
836         Checks in based on barcode
837         Returns record, status, text, circ, copy, route_to 
838         'status' values:
839                 0 = OK
840                 1 = 'copy required to fulfil a hold'
841                 2 = "copy is marked as lost"
842                 3 = "transit copy"
843                 4 = "transit for hold complete, put on holds shelf"
844         NOTES
845
846 sub checkin {
847         my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
848
849         my $err;
850         my $copy;
851         my $circ;
852         my $iamlost;
853
854         my $status = "0";
855         my $status_text = "OK";
856         my $route_to = "";
857
858         my $transaction;
859         my $user = $apputils->check_user_session($user_session);
860
861         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
862                 return OpenILS::Perm->new("COPY_CHECKIN");
863         }
864
865         my $session = $apputils->start_db_session();
866         my $transit_return;
867
868         my $orig_copy_status;
869
870
871         try {
872                         
873                 warn "retrieving copy for checkin\n";
874
875                         
876                 my $copy_req = $session->request(
877                         "open-ils.storage.direct.asset.copy.search.barcode.atomic", 
878                         $barcode );
879                 $copy = $copy_req->gather(1)->[0];
880                 if(!$copy) {
881                         $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
882                 }
883
884
885                 if($copy->status eq "3") { #if copy is lost
886                         $iamlost = 1;
887                 }
888
889                 if($copy->status eq "6") { #copy is in transit, deal with it
890                         my $method = $self->method_lookup("open-ils.circ.transit.receive");
891                         ($transit_return) = $method->run( $user_session, $copy->id );
892
893                 } else {
894
895
896                         if(!$shelving_locations) {
897                                 my $sh_req = $session->request(
898                                         "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
899                                 $shelving_locations = $sh_req->gather(1);
900                                 $shelving_locations = 
901                                         { map { (''.$_->id => $_->name) } @$shelving_locations };
902                         }
903         
904                         
905                         $orig_copy_status = $copy->status;
906                         $copy->status(0);
907                 
908                         # find circ's where the transaction is still open for the
909                         # given copy.  should only be one.
910                         warn "Retrieving circ for checkin\n";
911                         my $circ_req = $session->request(
912                                 "open-ils.storage.direct.action.circulation.search.atomic",
913                                 { target_copy => $copy->id, xact_finish => undef }, 
914                                 { order_by => "xact_start desc", limit => 1 } );
915                 
916                         $circ = $circ_req->gather(1)->[0];
917         
918                 
919                         if(!$circ) {
920                                 $err = "No circulation exists for the given barcode";
921         
922                         } else {
923         
924                                 $transaction = $session->request(
925                                         "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
926                 
927                                 warn "Checking in circ ". $circ->id . "\n";
928                         
929                                 $circ->stop_fines("CHECKIN");
930                                 $circ->stop_fines("RENEW") if($isrenewal);
931                                 $circ->stop_fines("LOST") if($iamlost);
932                                 $circ->xact_finish("now") if($transaction->balance_owed <= 0 and !$iamlost);
933
934                                 if($backdate) { 
935                                         $circ->xact_finish($backdate); 
936
937                                         # void any bills the resulted after the backdate time
938                                         my $bills = $session->request(
939                                                 "open-ils.storage.direct.money.billing.search_where.atomic",
940                                                 billing_ts => { ">=" => $backdate })->gather(1);
941
942                                         if($bills) {
943                                                 for my $bill (@$bills) {
944
945                                                         $bill->voided('t');
946                                                         my $s = $session->request(
947                                                                 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
948
949                                                         if(!$s) { 
950                                                                 throw OpenSRF::EX::ERROR 
951                                                                         ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
952                                                         }
953                                                 }
954                                         }
955                                 }
956                         
957                                 my $cp_up = $session->request(
958                                         "open-ils.storage.direct.asset.copy.update", $copy );
959                                 $cp_up->gather(1);
960                         
961                                 my $ci_up = $session->request(
962                                         "open-ils.storage.direct.action.circulation.update", $circ );
963
964
965                                 $ci_up->gather(1);
966                         
967                                 warn "Checkin succeeded\n";
968                         }
969                 }
970                 
971         } catch Error with {
972                 my $e = shift;
973                 $err = "Error checking in: $e";
974         };
975         
976         if($transit_return) { return $transit_return; }
977
978         if($err) {
979
980                 return { record => undef, status => -1, text => $err };
981
982         } else {
983
984
985                 # see if this copy can fulfill a hold
986                 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
987
988                 $route_to = $shelving_locations->{$copy->location};
989
990                 if($hold) { 
991                         warn "We found a hold that can be fulfilled by copy " . $copy->id . "\n";
992                         $status = "1";
993                         $status_text = "Copy needed to fulfill hold";
994                         $route_to = $hold->pickup_lib;
995                 }
996
997                 if($iamlost) {
998                         $status = "2";
999                         $status_text = "Copy is marked as LOST";
1000                 }
1001
1002                 if(!$hold and $copy->circ_lib ne $user->home_ou) {
1003
1004                         warn "Checked in copy needs to be transited " . $copy->id . "\n";
1005
1006                         my $transit = Fieldmapper::action::transit_copy->new;
1007                         $transit->source($user->home_ou);
1008                         $transit->dest($copy->circ_lib);
1009                         $transit->target_copy($copy->id);
1010                         $transit->source_send_time("now");
1011                         $transit->copy_status($orig_copy_status);
1012
1013                         my $s = $session->request(
1014                                 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1015
1016                         if(!$s){ throw OpenSRF::EX::ERROR 
1017                                 ("Unable to create new transit for copy " . $copy->id ); }
1018
1019                         warn "Putting copy into in transit state \n";
1020                         $copy->status(6); 
1021                         $copy->editor($user->id); 
1022                         $copy->edit_date("now"); 
1023
1024                         $s = $session->request(
1025                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
1026                         if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
1027
1028                         $status = 3;
1029                         $status_text = "Copy needs to be routed to a different location";
1030                         $route_to = $copy->circ_lib;
1031                 }
1032
1033
1034         
1035                 $apputils->commit_db_session($session);
1036
1037                 my $record = $apputils->simple_scalar_request(
1038                         "open-ils.storage",
1039                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1040                         $copy->id() );
1041
1042                 my $u = OpenILS::Utils::ModsParser->new();
1043                 $u->start_mods_batch( $record->marc() );
1044                 my $mods = $u->finish_mods_batch();
1045
1046                 return { 
1047                         record  => $mods, 
1048                         status  => $status,
1049                         text            => $status_text,
1050                         circ            => $circ,
1051                         copy            => $copy,
1052                         route_to => $route_to,
1053                 };
1054         }
1055
1056         return 1;
1057
1058 }
1059
1060
1061
1062
1063
1064 # ------------------------------------------------------------------------------
1065 # RENEWALS
1066 # ------------------------------------------------------------------------------
1067
1068
1069 __PACKAGE__->register_method(
1070         method  => "renew",
1071         api_name        => "open-ils.circ.renew",
1072         notes           => <<"  NOTES");
1073         open-ils.circ.renew(login_session, circ_object);
1074         Renews the provided circulation.  login_session is the requestor of the
1075         renewal and if the logged in user is not the same as circ->usr, then
1076         the logged in user must have RENEW_CIRC permissions.
1077         NOTES
1078
1079 sub renew {
1080         my($self, $client, $login_session, $circ) = @_;
1081
1082         throw OpenSRF::EX::InvalidArg 
1083                 ("open-ils.circ.renew no circ") unless defined($circ);
1084
1085         my $user = $apputils->check_user_session($login_session);
1086
1087         my $session = OpenSRF::AppSession->create("open-ils.storage");
1088         my $copy = _grab_copy_by_id($session, $circ->target_copy);
1089
1090         my $r = $session->request(
1091                 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
1092                 $copy->id )->gather(1);
1093
1094         my @holdids = map { $_->hold  } @$r;
1095
1096         if(@$r != 0) { 
1097
1098                 my $holds = $session->request(
1099                         "open-ils.storage.direct.action.hold_request.search_where", 
1100                                 { id => \@holdids, current_copy => undef } )->gather(1);
1101
1102                 if( $holds and $user->id ne $circ->usr ) {
1103                         if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
1104                                 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
1105                         }
1106                 }
1107
1108                 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex; 
1109         }
1110
1111
1112         if(!ref($circ)) {
1113                 $circ = $session->request(
1114                         "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
1115         }
1116
1117         my $iid = $circ->id;
1118         warn "Attempting to renew circ " . $iid . "\n";
1119
1120         if($user->id ne $circ->usr) {
1121                 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
1122                         return OpenILS::Perm->new("RENEW_CIRC");
1123                 }
1124         }
1125
1126         if($circ->renewal_remaining <= 0) {
1127                 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1128
1129
1130
1131         # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1132         # XXX check overdue..?
1133
1134         my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1135         my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1136         return $status if ($status->{status} ne "0"); 
1137         warn "Renewal checkin completed for $iid\n";
1138
1139         my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1140         ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1141         return $status if($status->{status} ne "0");
1142         warn "Renewal permit checkout completed for $iid\n";
1143
1144         my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1145         ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1146         warn "Renewal checkout completed for $iid\n";
1147         return $status;
1148
1149 }
1150
1151
1152
1153 1;