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