]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
circ.checkedout only returns items that are actually checked out
[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                         "open-ils.storage.direct.permission.grp_tree.retrieve.all.atomic");
126                 $patron_profiles = $profile_req->gather(1);
127                 $patron_profiles =
128                         { map { (''.$_->id => $_->name) } @$patron_profiles };
129         }
130
131 }
132
133 sub _grab_user {
134         my $session = shift;
135         my $patron_id = shift;
136         my $patron_req  = $session->request(
137                 "open-ils.storage.direct.actor.user.retrieve", 
138                 $patron_id );
139         return $patron_req->gather(1);
140 }
141         
142
143 sub _grab_title_by_copy {
144         my $session = shift;
145         my $copyid = shift;
146         my $title_req   = $session->request(
147                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
148                 $copyid );
149         return $title_req->gather(1);
150 }
151
152 sub _grab_patron_summary {
153         my $session = shift;
154         my $patron_id = shift;
155         my $summary_req = $session->request(
156                 "open-ils.storage.action.circulation.patron_summary",
157                 $patron_id );
158         return $summary_req->gather(1);
159 }
160
161 sub _grab_copy_by_barcode {
162         my($session, $barcode) = @_;
163         warn "Searching for copy with barcode $barcode\n";
164         my $copy_req    = $session->request(
165                 "open-ils.storage.fleshed.asset.copy.search.barcode", $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) unless ref($copy->circ_lib);
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} = $$args{requestor} || _grab_user($session, $hold->requestor);
197         my $patron                                              = $$args{usr} || _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.... and setting num renews to " . $numrenews - 1 . "\n";
476                 $circ->opac_renewal(1); # XXX different for different types ! 
477                 $circ->clear_id;
478                 $circ->renewal_remaining($numrenews - 1);
479                 $circ->circ_staff($patron);
480         } else {
481                 $circ->circ_staff($user->id);
482         }
483
484
485         # commit new circ object to db
486         my $commit = $session->request(
487                 "open-ils.storage.direct.action.circulation.create", $circ );
488         my $id = $commit->gather(1);
489
490         if(!$id) {
491                 throw OpenSRF::EX::ERROR 
492                         ("Error creating new circulation object");
493         }
494
495         # update the copy with the new circ
496         $copy->status( $stash->get("target_copy_status") );
497         $copy->location( $copy->location->id );
498         $copy->circ_lib( $copy->circ_lib->id ); #XXX XXX needs to point to the lib that actually checked out the item (user->home_ou)?
499
500         # commit copy to db
501         my $copy_update = $session->request(
502                 "open-ils.storage.direct.asset.copy.update",
503                 $copy );
504         $copy_update->gather(1);
505
506
507         if( $realstatus eq "8" ) { # on holds shelf
508
509
510                 warn "Searching for hold for checkout of copy " . $copy->id . "\n";
511
512                 my $hold = $session->request(
513                         "open-ils.storage.direct.action.hold_request.search.atomic",
514                          current_copy =>  $copy->id , fulfillment_time => undef )->gather(1);
515                 $hold = $hold->[0];
516
517                 if($hold) {
518
519                         $hold->fulfillment_time("now");
520                                 my $s = $session->request(
521                                         "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
522                                 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating hold");}
523                 }
524         }
525
526         $apputils->commit_db_session($session);
527
528         # remove our circ object from the cache
529         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
530
531         # re-retrieve the the committed circ object  
532         $circ = $apputils->simple_scalar_request(
533                 "open-ils.storage",
534                 "open-ils.storage.direct.action.circulation.retrieve",
535                 $id );
536
537
538         # push the rules and due date into the circ object
539         $circ->duration_rule($duration);
540         $circ->max_fine_rule($max);
541         $circ->recuring_fine_rule($recurring);
542
543         # turn the biblio record into a friendly object
544         my $obj = $stash->get("circ_objects");
545         my $u = OpenILS::Utils::ModsParser->new();
546         $u->start_mods_batch( $circ_objects->{title}->marc() );
547         my $mods = $u->finish_mods_batch();
548
549
550         return { circ => $circ, copy => $copy, record => $mods };
551
552 }
553
554
555
556 # runs the duration, recurring_fines, and max_fines scripts.
557 # builds the new circ object based on the rules returned from 
558 # these scripts. 
559 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
560 sub run_circ_scripts {
561         my $session = shift;
562
563         # go through all of the scripts and process
564         # each script returns 
565         # [ rule_name, level (appropriate to the script) ]
566         $stash->set("result", [] );
567         $stash->set("run_block", $duration_script);
568         run_script();
569         my $duration_rule = $stash->get("result");
570
571         $stash->set("run_block", $recurring_fines_script);
572         $stash->set("result", [] );
573         run_script();
574         my $rec_fines_rule = $stash->get("result");
575
576         $stash->set("run_block", $max_fines_script);
577         $stash->set("result", [] );
578         run_script();
579         my $max_fines_rule = $stash->get("result");
580
581         my $obj = $stash->get("circ_objects");
582
583         # ----------------------------------------------------------
584         # find the rules objects based on the rule names returned from
585         # the various scripts.
586         my $dur_req = $session->request(
587                 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
588                 $duration_rule->[0] );
589
590         my $rec_req = $session->request(
591                 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
592                 $rec_fines_rule->[0] );
593
594         my $max_req = $session->request(
595                 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
596                 $max_fines_rule->[0] );
597
598         my $duration    = $dur_req->gather(1)->[0];
599         my $recurring   = $rec_req->gather(1)->[0];
600         my $max                 = $max_req->gather(1)->[0];
601
602         my $copy = $circ_objects->{copy};
603
604         use Data::Dumper;
605         warn "Building a new circulation object with\n".
606                 "=> copy "                              . Dumper($copy) .
607                 "=> duration_rule "     . Dumper($duration_rule) .
608                 "=> rec_files_rule " . Dumper($rec_fines_rule) .
609                 "=> duration "                  . Dumper($duration) .
610                 "=> recurring "         . Dumper($recurring) .
611                 "=> max "                               . Dumper($max);
612
613
614         # build the new circ object
615         my $circ =  build_circ_object($session, $copy, $duration_rule->[1], 
616                         $rec_fines_rule->[1], $duration, $recurring, $max );
617
618         return ($circ, $duration, $recurring, $max);
619
620 }
621
622 # ------------------------------------------------------------------
623 # Builds a new circ object
624 # ------------------------------------------------------------------
625 sub build_circ_object {
626         my( $session, $copy, $dur_level, $rec_level, 
627                         $duration, $recurring, $max ) = @_;
628
629         my $circ = new Fieldmapper::action::circulation;
630
631         $circ->circ_lib( $copy->circ_lib->id() );
632         if($dur_level == 1) {
633                 $circ->duration( $duration->shrt );
634         } elsif($dur_level == 2) {
635                 $circ->duration( $duration->normal );
636         } elsif($dur_level == 3) {
637                 $circ->duration( $duration->extended );
638         }
639
640         if($rec_level == 1) {
641                 $circ->recuring_fine( $recurring->low );
642         } elsif($rec_level == 2) {
643                 $circ->recuring_fine( $recurring->normal );
644         } elsif($rec_level == 3) {
645                 $circ->recuring_fine( $recurring->high );
646         }
647
648         $circ->duration_rule( $duration->name );
649         $circ->recuring_fine_rule( $recurring->name );
650         $circ->max_fine_rule( $max->name );
651         $circ->max_fine( $max->amount );
652
653         $circ->fine_interval($recurring->recurance_interval);
654         $circ->renewal_remaining( $duration->max_renewals );
655         $circ->target_copy( $copy->id );
656         $circ->usr( $circ_objects->{patron}->id );
657
658         return $circ;
659
660 }
661
662 __PACKAGE__->register_method(
663         method  => "transit_receive",
664         api_name        => "open-ils.circ.transit.receive",
665         notes           => <<"  NOTES");
666         Receives a copy that is in transit.  
667         Params are login_session and copyid.
668         Logged in user must have COPY_CHECKIN priveleges.
669
670         status 3 means that this transit is destined for somewhere else
671         status 10 means the copy is not in transit
672         status 11 means the transit is complete, does not need processing
673         status 12 means copy is in transit but no tansit was found
674
675         NOTES
676
677 sub transit_receive {
678         my( $self, $client, $login_session, $copyid ) = @_;
679
680         my $user = $apputils->check_user_session($login_session);
681
682         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
683                 return OpenILS::Perm->new("COPY_CHECKIN");
684         }
685
686         warn "Receiving copy for transit $copyid\n";
687
688         my $session = $apputils->start_db_session();
689         my $copy = $session->request(
690                         "open-ils.storage.direct.asset.copy.retrieve", $copyid)->gather(1);
691         my $transit;
692
693         if(!$copy->status eq "6") {
694                 return { status => 10, route_to => $copy->circ_lib };
695         }
696
697
698         $transit = $session->request(
699                 "open-ils.storage.direct.action.transit_copy.search_where",
700                 { target_copy => $copy->id, dest_recv_time => undef } )->gather(1);
701
702
703         my $record = _grab_title_by_copy($session, $copy->id);
704         my $u = OpenILS::Utils::ModsParser->new();
705         $u->start_mods_batch( $record->marc() );
706         $record = $u->finish_mods_batch();
707
708         if($transit) {
709
710                 warn "Found transit " . $transit->id . " for copy $copyid\n";
711
712                 if( defined($transit->dest_recv_time) ) {
713                         return { status => 11, route_to => $copy->circ_lib, 
714                                 text => "Transit is already complete for this copy" };
715                 }
716
717                 $transit->dest_recv_time("now");
718                 my $s = $session->request(
719                         "open-ils.storage.direct.action.transit_copy.update", $transit )->gather(1);
720
721                 if(!$s) { throw OpenSRF::EX::ERROR ("Error updating transit " . $transit->id . "\n"); }
722
723                 warn "Searching for hold transit with id " . $transit->id . "\n";
724
725                 my $holdtransit = $session->request(
726                         "open-ils.storage.direct.action.hold_transit_copy.retrieve",
727                         $transit->id )->gather(1);
728
729                 if($holdtransit) {
730
731                         warn "Found hold transit for copy $copyid\n";
732
733                         my $hold = $session->request(
734                                 "open-ils.storage.direct.action.hold_request.retrieve",
735                                 $holdtransit->hold )->gather(1);
736
737                         if(!$hold) {
738                                 throw OpenSRF::EX::ERROR ("No hold found to match transit " . $holdtransit->id);
739                         }
740
741                         # put copy on the holds shelf
742                         $copy->status(8); #hold shelf status
743                         $copy->editor($user->id); #hold shelf status
744                         $copy->edit_date("now"); #hold shelf status
745
746                         warn "Updating copy " . $copy->id . " with new status, editor, and edit date\n";
747
748                         my $s = $session->request(
749                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
750                         if(!$s) {throw OpenSRF::EX::ERROR ("Error putting copy on holds shelf ".$copy->id);} # blah..
751
752                         $apputils->commit_db_session($session);
753
754                         return { status => 4, route_to => "Holds Shelf", 
755                                 text => "Transit Complete", record => $record, copy => $copy  };
756
757
758                 } else {
759
760                         if($transit->dest eq $user->home_ou) {
761
762                                 $copy->status(0); 
763                                 $copy->editor($user->id); 
764                                 $copy->edit_date("now"); 
765
766                                 my $s = $session->request(
767                                         "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
768                                 if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
769
770                                 my($status, $status_text) = (0, "Transit Complete");
771
772                                 my $circ;
773
774                                 if($transit->copy_status eq "3") { #if copy is lost
775                                         $status = 2;
776                                         $status_text = "Copy is marked as LOST";
777
778                                         $circ = $session->request(
779                                                 "open-ils.storage.direct.action.circulation.search_where",
780                                                 { target_copy => $copy->id, xact_finish => undef }, 
781                                                 { order_by => "xact_start desc", limit => 1 } )->gather(1);
782
783
784                                         if($circ) {
785
786                                                 my $transaction = $session->request(
787                                                         "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
788
789                                                 $circ->xact_finish("now") if($transaction->balance_owed <= 0);
790
791                                                 my $s = $session->request(
792                                                         "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
793
794                                                 if(!$s) {
795                                                         throw OpenSRF::EX::ERROR ("Error updating circulation " . $circ->id);
796                                                 }
797                                         }
798
799                                 }
800
801                                 $apputils->commit_db_session($session);
802
803                                 return { 
804                                         status => $status, 
805                                         route_to => $user->home_ou, 
806                                         text => $status_text, 
807                                         record => $record, 
808                                         circ    => $circ,
809                                         copy => $copy  };
810
811                         } else {
812
813                                 $apputils->rollback_db_session($session);
814
815
816                                 return { 
817                                         copy => $copy, record => $record, 
818                                         status => 3, route_to => $transit->dest, 
819                                         text => "Transit needs to be routed" };
820
821                         }
822
823                 }
824
825
826         } else { 
827
828                 $apputils->rollback_db_session($session);
829                 return { status => 12, route_to => $copy->circ_lib, text => "No transit found" };
830         } 
831
832 }
833
834
835
836 __PACKAGE__->register_method(
837         method  => "checkin",
838         api_name        => "open-ils.circ.checkin.barcode",
839         notes           => <<"  NOTES");
840         Checks in based on barcode
841         Returns record, status, text, circ, copy, route_to 
842         'status' values:
843                 0 = OK
844                 1 = 'copy required to fulfil a hold'
845                 2 = "copy is marked as lost"
846                 3 = "transit copy"
847                 4 = "transit for hold complete, put on holds shelf"
848         NOTES
849
850 sub checkin {
851         my( $self, $client, $user_session, $barcode, $isrenewal, $backdate ) = @_;
852
853         my $err;
854         my $copy;
855         my $circ;
856         my $iamlost;
857
858         my $status = "0";
859         my $status_text = "OK";
860         my $route_to = "";
861
862         my $transaction;
863         my $user = $apputils->check_user_session($user_session);
864
865         if($apputils->check_user_perms($user->id, $user->home_ou, "COPY_CHECKIN")) {
866                 return OpenILS::Perm->new("COPY_CHECKIN");
867         }
868
869         my $session = $apputils->start_db_session();
870         my $transit_return;
871
872         my $orig_copy_status;
873
874
875         try {
876                         
877                 warn "retrieving copy for checkin\n";
878
879                         
880                 my $copy_req = $session->request(
881                         "open-ils.storage.direct.asset.copy.search.barcode.atomic", 
882                         $barcode );
883                 $copy = $copy_req->gather(1)->[0];
884                 if(!$copy) {
885                         $client->respond_complete(OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
886                 }
887
888
889                 if($copy->status eq "3") { #if copy is lost
890                         $iamlost = 1;
891                 }
892
893                 if($copy->status eq "6") { #copy is in transit, deal with it
894                         my $method = $self->method_lookup("open-ils.circ.transit.receive");
895                         ($transit_return) = $method->run( $user_session, $copy->id );
896
897                 } else {
898
899
900                         if(!$shelving_locations) {
901                                 my $sh_req = $session->request(
902                                         "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
903                                 $shelving_locations = $sh_req->gather(1);
904                                 $shelving_locations = 
905                                         { map { (''.$_->id => $_->name) } @$shelving_locations };
906                         }
907         
908                         
909                         $orig_copy_status = $copy->status;
910                         $copy->status(0);
911                 
912                         # find circ's where the transaction is still open for the
913                         # given copy.  should only be one.
914                         warn "Retrieving circ for checkin\n";
915                         my $circ_req = $session->request(
916                                 "open-ils.storage.direct.action.circulation.search.atomic",
917                                 { target_copy => $copy->id, xact_finish => undef }, 
918                                 { order_by => "xact_start desc", limit => 1 } );
919                 
920                         $circ = $circ_req->gather(1)->[0];
921         
922                 
923                         if(!$circ) {
924                                 $err = "No circulation exists for the given barcode";
925         
926                         } else {
927         
928                                 $transaction = $session->request(
929                                         "open-ils.storage.direct.money.billable_transaction_summary.retrieve", $circ->id)->gather(1);
930                 
931                                 warn "Checking in circ ". $circ->id . "\n";
932                         
933                                 $circ->stop_fines("CHECKIN");
934                                 $circ->stop_fines("RENEW") if($isrenewal);
935                                 $circ->stop_fines("LOST") if($iamlost);
936                                 $circ->xact_finish("now") if($transaction->balance_owed <= 0 and !$iamlost);
937
938                                 if($backdate) { 
939                                         $circ->xact_finish($backdate); 
940
941                                         # void any bills the resulted after the backdate time
942                                         my $bills = $session->request(
943                                                 "open-ils.storage.direct.money.billing.search_where.atomic",
944                                                 billing_ts => { ">=" => $backdate })->gather(1);
945
946                                         if($bills) {
947                                                 for my $bill (@$bills) {
948
949                                                         $bill->voided('t');
950                                                         my $s = $session->request(
951                                                                 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
952
953                                                         if(!$s) { 
954                                                                 throw OpenSRF::EX::ERROR 
955                                                                         ("Error voiding bill on checkin with backdate : $backdate, circ id: " . $circ->id);
956                                                         }
957                                                 }
958                                         }
959                                 }
960                         
961                                 my $cp_up = $session->request(
962                                         "open-ils.storage.direct.asset.copy.update", $copy );
963                                 $cp_up->gather(1);
964                         
965                                 my $ci_up = $session->request(
966                                         "open-ils.storage.direct.action.circulation.update", $circ );
967
968
969                                 $ci_up->gather(1);
970                         
971                                 warn "Checkin succeeded\n";
972                         }
973                 }
974                 
975         } catch Error with {
976                 my $e = shift;
977                 $err = "Error checking in: $e";
978         };
979         
980         if($transit_return) { return $transit_return; }
981
982         if($err) {
983
984                 return { record => undef, status => -1, text => $err };
985
986         } else {
987
988
989                 # see if this copy can fulfill a hold
990                 my $hold = OpenILS::Application::Circ::Holds::_find_local_hold_for_copy( $session, $copy, $user );
991
992                 $route_to = $shelving_locations->{$copy->location};
993
994                 if($hold) { 
995                         warn "We found a hold that can be fulfilled by copy " . $copy->id . "\n";
996                         $status = "1";
997                         $status_text = "Copy needed to fulfill hold";
998                         $route_to = $hold->pickup_lib;
999                 }
1000
1001                 if($iamlost) {
1002                         $status = "2";
1003                         $status_text = "Copy is marked as LOST";
1004                 }
1005
1006                 if(!$hold and $copy->circ_lib ne $user->home_ou) {
1007
1008                         warn "Checked in copy needs to be transited " . $copy->id . "\n";
1009
1010                         my $transit = Fieldmapper::action::transit_copy->new;
1011                         $transit->source($user->home_ou);
1012                         $transit->dest($copy->circ_lib);
1013                         $transit->target_copy($copy->id);
1014                         $transit->source_send_time("now");
1015                         $transit->copy_status($orig_copy_status);
1016
1017                         my $s = $session->request(
1018                                 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1019
1020                         if(!$s){ throw OpenSRF::EX::ERROR 
1021                                 ("Unable to create new transit for copy " . $copy->id ); }
1022
1023                         warn "Putting copy into in transit state \n";
1024                         $copy->status(6); 
1025                         $copy->editor($user->id); 
1026                         $copy->edit_date("now"); 
1027
1028                         $s = $session->request(
1029                                 "open-ils.storage.direct.asset.copy.update", $copy )->gather(1);
1030                         if(!$s) {throw OpenSRF::EX::ERROR ("Error updating copy ".$copy->id);} # blah..
1031
1032                         $status = 3;
1033                         $status_text = "Copy needs to be routed to a different location";
1034                         $route_to = $copy->circ_lib;
1035                 }
1036
1037
1038         
1039                 $apputils->commit_db_session($session);
1040
1041                 my $record = $apputils->simple_scalar_request(
1042                         "open-ils.storage",
1043                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1044                         $copy->id() );
1045
1046                 my $u = OpenILS::Utils::ModsParser->new();
1047                 $u->start_mods_batch( $record->marc() );
1048                 my $mods = $u->finish_mods_batch();
1049
1050                 return { 
1051                         record  => $mods, 
1052                         status  => $status,
1053                         text            => $status_text,
1054                         circ            => $circ,
1055                         copy            => $copy,
1056                         route_to => $route_to,
1057                 };
1058         }
1059
1060         return 1;
1061
1062 }
1063
1064
1065
1066
1067
1068 # ------------------------------------------------------------------------------
1069 # RENEWALS
1070 # ------------------------------------------------------------------------------
1071
1072
1073 __PACKAGE__->register_method(
1074         method  => "renew",
1075         api_name        => "open-ils.circ.renew",
1076         notes           => <<"  NOTES");
1077         open-ils.circ.renew(login_session, circ_object);
1078         Renews the provided circulation.  login_session is the requestor of the
1079         renewal and if the logged in user is not the same as circ->usr, then
1080         the logged in user must have RENEW_CIRC permissions.
1081         NOTES
1082
1083 sub renew {
1084         my($self, $client, $login_session, $circ) = @_;
1085
1086         throw OpenSRF::EX::InvalidArg 
1087                 ("open-ils.circ.renew no circ") unless defined($circ);
1088
1089         my $user = $apputils->check_user_session($login_session);
1090
1091         my $session = OpenSRF::AppSession->create("open-ils.storage");
1092         my $copy = _grab_copy_by_id($session, $circ->target_copy);
1093
1094         my $r = $session->request(
1095                 "open-ils.storage.direct.action.hold_copy_map.search.target_copy.atomic",
1096                 $copy->id )->gather(1);
1097
1098         my @holdids = map { $_->hold  } @$r;
1099
1100         if(@$r != 0) { 
1101
1102                 my $holds = $session->request(
1103                         "open-ils.storage.direct.action.hold_request.search_where", 
1104                                 { id => \@holdids, current_copy => undef } )->gather(1);
1105
1106                 if( $holds and $user->id ne $circ->usr ) {
1107                         if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_HOLD_OVERRIDE")) {
1108                                 return OpenILS::Perm->new("RENEW_HOLD_OVERRIDE");
1109                         }
1110                 }
1111
1112                 return OpenILS::EX->new("COPY_NEEDED_FOR_HOLD")->ex; 
1113         }
1114
1115
1116         if(!ref($circ)) {
1117                 $circ = $session->request(
1118                         "open-ils.storage.direct.action.circulation.retrieve", $circ )->gather(1);
1119         }
1120
1121         my $iid = $circ->id;
1122         warn "Attempting to renew circ " . $iid . "\n";
1123
1124         if($user->id ne $circ->usr) {
1125                 if($apputils->check_user_perms($user->id, $user->home_ou, "RENEW_CIRC")) {
1126                         return OpenILS::Perm->new("RENEW_CIRC");
1127                 }
1128         }
1129
1130         if($circ->renewal_remaining <= 0) {
1131                 return OpenILS::EX->new("MAX_RENEWALS_REACHED")->ex; }
1132
1133
1134
1135         # XXX XXX See if the copy this circ points to is needed to fulfill a hold!
1136         # XXX check overdue..?
1137
1138         use Data::Dumper;
1139         my $checkin = $self->method_lookup("open-ils.circ.checkin.barcode");
1140         my ($status) = $checkin->run($login_session, $copy->barcode, 1);
1141         warn 'checkin status: ' . Dumper($status) . '\n';
1142         return $status if ref($status) eq "Fieldmapper::perm_ex";
1143         return $status if ($status->{status} ne "0"); 
1144         warn "Renewal checkin completed for $iid\n";
1145
1146         my $permit_checkout = $self->method_lookup("open-ils.circ.permit_checkout");
1147         ($status) = $permit_checkout->run($login_session, $copy->barcode, $circ->usr, "renew");
1148         warn 'permit checkout status: ' . Dumper($status) . '\n';
1149         return $status if ref($status) eq "Fieldmapper::perm_ex";
1150         return $status if($status->{status} ne "0");
1151         warn "Renewal permit checkout completed for $iid\n";
1152
1153         my $checkout = $self->method_lookup("open-ils.circ.checkout.barcode");
1154         ($status) = $checkout->run($login_session, $copy->barcode, $circ->usr, 1, $circ->renewal_remaining);
1155         warn 'renew checkout status: ' . Dumper($status) . '\n';
1156         return $status if ref($status) eq "Fieldmapper::perm_ex";
1157         warn "Renewal checkout completed for $iid\n";
1158         return $status;
1159
1160 }
1161
1162
1163
1164 1;