]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
fixed some logic errors, added barred checking
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Circulate.pm
1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
5 use Data::Dumper;
6 use OpenSRF::Utils::Cache;
7 use OpenSRF::AppSession;
8 use Digest::MD5 qw(md5_hex);
9 use OpenILS::Utils::ScriptRunner;
10 use OpenILS::Application::AppUtils;
11 use OpenILS::Application::Circ::Holds;
12 use OpenILS::Application::Circ::Transit;
13 use OpenILS::Utils::PermitHold;
14 use OpenSRF::Utils::Logger qw(:logger);
15 use DateTime;
16 use DateTime::Format::ISO8601;
17 use OpenSRF::Utils qw/:datetime/;
18
19 $Data::Dumper::Indent = 0;
20 my $apputils    = "OpenILS::Application::AppUtils";
21 my $U                           = $apputils;
22 my $holdcode    = "OpenILS::Application::Circ::Holds";
23 my $transcode   = "OpenILS::Application::Circ::Transit";
24
25 my %scripts;                    # - circulation script filenames
26 my $script_libs;                # - any additional script libraries
27 my %cache;                              # - db objects cache
28 my %contexts;                   # - Script runner contexts
29 my $cache_handle;               # - memcache handle
30
31 sub PRECAT_FINE_LEVEL { return 2; }
32 sub PRECAT_LOAN_DURATION { return 2; }
33
34 my %RECORD_FROM_COPY_CACHE;
35
36
37 # for security, this is a process-defined and not
38 # a client-defined variable
39 my $__isrenewal = 0;
40
41 # ------------------------------------------------------------------------------
42 # Load the circ script from the config
43 # ------------------------------------------------------------------------------
44 sub initialize {
45
46         my $self = shift;
47         $cache_handle = OpenSRF::Utils::Cache->new('global');
48         my $conf = OpenSRF::Utils::SettingsClient->new;
49         my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
50         my @pfx = ( @pfx2, "scripts" );
51
52         my $p           = $conf->config_value(  @pfx, 'circ_permit_patron' );
53         my $c           = $conf->config_value(  @pfx, 'circ_permit_copy' );
54         my $d           = $conf->config_value(  @pfx, 'circ_duration' );
55         my $f           = $conf->config_value(  @pfx, 'circ_recurring_fines' );
56         my $m           = $conf->config_value(  @pfx, 'circ_max_fines' );
57         my $pr  = $conf->config_value(  @pfx, 'circ_permit_renew' );
58         my $lb  = $conf->config_value(  @pfx2, 'script_path' );
59
60         $logger->error( "Missing circ script(s)" ) 
61                 unless( $p and $c and $d and $f and $m and $pr );
62
63         $scripts{circ_permit_patron}    = $p;
64         $scripts{circ_permit_copy}              = $c;
65         $scripts{circ_duration}                 = $d;
66         $scripts{circ_recurring_fines}= $f;
67         $scripts{circ_max_fines}                = $m;
68         $scripts{circ_permit_renew}     = $pr;
69
70         $lb = [ $lb ] unless ref($lb);
71         $script_libs = $lb;
72
73         $logger->debug("Loaded rules scripts for circ: " .
74                 "circ permit patron: $p, circ permit copy: $c, ".
75                 "circ duration :$d , circ recurring fines : $f, " .
76                 "circ max fines : $m, circ renew permit : $pr");
77 }
78
79
80 # ------------------------------------------------------------------------------
81 # Loads the necessary circ objects and pushes them into the script environment
82 # Returns ( $data, $evt ).  if $evt is defined, then an
83 # unexpedted event occurred and should be dealt with / returned to the caller
84 # ------------------------------------------------------------------------------
85 sub create_circ_ctx {
86         my %params = @_;
87         $U->logmark;
88
89         my $evt;
90         my $ctx = \%params;
91
92         $evt = _ctx_add_patron_objects($ctx, %params);
93         return (undef,$evt) if $evt;
94
95         if(!$params{noncat}) {
96                 if( $evt = _ctx_add_copy_objects($ctx, %params) ) {
97                         $ctx->{precat} = 1 if($evt->{textcode} eq 'ASSET_COPY_NOT_FOUND')
98                 } else {
99                         $ctx->{precat} = 1 if ( $ctx->{copy}->call_number == -1 ); # special case copy
100                 }
101         }
102
103         _doctor_patron_object($ctx) if $ctx->{patron};
104         _doctor_copy_object($ctx) if $ctx->{copy};
105
106         if(!$ctx->{no_runner}) {
107                 _build_circ_script_runner($ctx);
108                 _add_script_runner_methods($ctx);
109         }
110
111         return $ctx;
112 }
113
114 sub _ctx_add_patron_objects {
115         my( $ctx, %params) = @_;
116         $U->logmark;
117
118         # - patron standings are now handled in the penalty server...
119
120         #if(!defined($cache{patron_standings})) {
121         #       $cache{patron_standings} = $U->fetch_patron_standings();
122         #}
123         #$ctx->{patron_standings} = $cache{patron_standings};
124
125         $cache{group_tree} = $U->fetch_permission_group_tree() unless $cache{group_tree};
126         $ctx->{group_tree} = $cache{group_tree};
127
128         $ctx->{patron_circ_summary} = 
129                 $U->fetch_patron_circ_summary($ctx->{patron}->id) 
130                 if $params{fetch_patron_circsummary};
131
132         return undef;
133 }
134
135
136 sub _find_copy_by_attr {
137         my %params = @_;
138         $U->logmark;
139         my $evt;
140
141         my $copy = $params{copy} || undef;
142
143         if(!$copy) {
144
145                 ( $copy, $evt ) = 
146                         $U->fetch_copy($params{copyid}) if $params{copyid};
147                 return (undef,$evt) if $evt;
148
149                 if(!$copy) {
150                         ( $copy, $evt ) = 
151                                 $U->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
152                         return (undef,$evt) if $evt;
153                 }
154         }
155         return ( $copy, $evt );
156 }
157
158 sub _ctx_add_copy_objects {
159         my($ctx, %params)  = @_;
160         $U->logmark;
161         my $evt;
162         my $copy;
163
164         $cache{copy_statuses} = $U->fetch_copy_statuses 
165                 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
166
167         $cache{copy_locations} = $U->fetch_copy_locations 
168                 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
169
170         $ctx->{copy_statuses} = $cache{copy_statuses};
171         $ctx->{copy_locations} = $cache{copy_locations};
172
173         ($copy, $evt) = _find_copy_by_attr(%params);
174         return $evt if $evt;
175
176         if( $copy and !$ctx->{title} ) {
177                 $logger->debug("Copy status: " . $copy->status);
178
179                 my $r = $RECORD_FROM_COPY_CACHE{$copy->id};
180                 ($r, $evt) = $U->fetch_record_by_copy( $copy->id ) unless $r;
181                 return $evt if $evt;
182                 $RECORD_FROM_COPY_CACHE{$copy->id} = $r;
183
184                 $ctx->{title} = $r;
185                 $ctx->{copy} = $copy;
186         }
187
188         return undef;
189 }
190
191
192 # ------------------------------------------------------------------------------
193 # Fleshes parts of the patron object
194 # ------------------------------------------------------------------------------
195 sub _doctor_copy_object {
196         my $ctx = shift;
197         $U->logmark;
198         my $copy = $ctx->{copy} || return undef;
199
200         $logger->debug("Doctoring copy object...");
201
202         # set the copy status to a status name
203         $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
204
205         # set the copy location to the location object
206         $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
207
208         $copy->circ_lib( $U->fetch_org_unit($copy->circ_lib) );
209 }
210
211
212 # ------------------------------------------------------------------------------
213 # Fleshes parts of the patron object
214 # ------------------------------------------------------------------------------
215 sub _doctor_patron_object {
216         my $ctx = shift;
217         $U->logmark;
218         my $patron = $ctx->{patron} || return undef;
219
220         # push the standing object into the patron
221 #       if(ref($ctx->{patron_standings})) {
222 #               for my $s (@{$ctx->{patron_standings}}) {
223 #                       if( $s->id eq $ctx->{patron}->standing ) {
224 #                               $patron->standing($s);
225 #                               $logger->debug("Set patron standing to ". $s->value);
226 #                       }
227 #               }
228 #       }
229
230         # set the patron ptofile to the profile name
231         $patron->profile( _get_patron_profile( 
232                 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
233
234         # flesh the org unit
235         $patron->home_ou( 
236                 $U->fetch_org_unit( $patron->home_ou ) ) if $patron;
237
238 }
239
240 # recurse and find the patron profile name from the tree
241 # another option would be to grab the groups for the patron
242 # and cycle through those until the "profile" group has been found
243 sub _get_patron_profile { 
244         my( $patron, $group_tree ) = @_;
245         return $group_tree if ($group_tree->id eq $patron->profile);
246         return undef unless ($group_tree->children);
247
248         for my $child (@{$group_tree->children}) {
249                 my $ret = _get_patron_profile( $patron, $child );
250                 return $ret if $ret;
251         }
252         return undef;
253 }
254
255 sub _get_copy_status {
256         my( $copy, $cstatus ) = @_;
257         $U->logmark;
258         my $s = undef;
259         for my $status (@$cstatus) {
260                 $s = $status if( $status->id eq $copy->status ) 
261         }
262         $logger->debug("Retrieving copy status: " . $s->name) if $s;
263         return $s;
264 }
265
266 sub _get_copy_location {
267         my( $copy, $locations ) = @_;
268         $U->logmark;
269         my $l = undef;
270         for my $loc (@$locations) {
271                 $l = $loc if $loc->id eq $copy->location;
272         }
273         $logger->debug("Retrieving copy location: " . $l->name ) if $l;
274         return $l;
275 }
276
277
278 # ------------------------------------------------------------------------------
279 # Constructs and shoves data into the script environment
280 # ------------------------------------------------------------------------------
281 sub _build_circ_script_runner {
282         my $ctx = shift;
283         $U->logmark;
284
285         $logger->debug("Loading script environment for circulation");
286
287         my $runner;
288         if( $runner = $contexts{$ctx->{type}} ) {
289                 $runner->refresh_context;
290         } else {
291                 $runner = OpenILS::Utils::ScriptRunner->new;
292                 $contexts{type} = $runner;
293         }
294
295         for(@$script_libs) {
296                 $logger->debug("Loading circ script lib path $_");
297                 $runner->add_path( $_ );
298         }
299
300         # Note: inserting the number 0 into the script turns into the
301         # string "0", and thus evaluates to true in JS land
302         # inserting undef will insert "", which evaluates to false
303
304         $runner->insert( 'environment.patron',  $ctx->{patron}, 1);
305         $runner->insert( 'environment.title',   $ctx->{title}, 1);
306         $runner->insert( 'environment.copy',    $ctx->{copy}, 1);
307
308         # circ script result
309         $runner->insert( 'result', {} );
310         #$runner->insert( 'result.event', 'SUCCESS' );
311         $runner->insert( 'result.events', [] );
312
313         if($__isrenewal) {
314                 $runner->insert('environment.isRenewal', 1);
315         } else {
316                 $runner->insert('environment.isRenewal', undef);
317         }
318
319         if($ctx->{ishold} ) { 
320                 $runner->insert('environment.isHold', 1); 
321         } else{ 
322                 $runner->insert('environment.isHold', undef) 
323         }
324
325         if( $ctx->{noncat} ) {
326                 $runner->insert('environment.isNonCat', 1);
327                 $runner->insert('environment.nonCatType', $ctx->{noncat_type});
328         } else {
329                 $runner->insert('environment.isNonCat', undef);
330         }
331
332 #       if(ref($ctx->{patron_circ_summary})) {
333 #               $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
334 #               $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
335 #       }
336
337         $ctx->{runner} = $runner;
338         return $runner;
339 }
340
341
342 sub _add_script_runner_methods {
343         my $ctx = shift;
344         $U->logmark;
345         my $runner = $ctx->{runner};
346
347         if( $ctx->{copy} ) {
348                 
349                 # allows a script to fetch a hold that is currently targeting the
350                 # copy in question
351                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
352                                 my $key = shift;
353                                 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
354                                 $hold = undef unless $hold;
355                                 $runner->insert( $key, $hold, 1 );
356                         }
357                 );
358         }
359 }
360
361 # ------------------------------------------------------------------------------
362
363 __PACKAGE__->register_method(
364         method  => "permit_circ",
365         api_name        => "open-ils.circ.checkout.permit",
366         notes           => q/
367                 Determines if the given checkout can occur
368                 @param authtoken The login session key
369                 @param params A trailing hash of named params including 
370                         barcode : The copy barcode, 
371                         patron : The patron the checkout is occurring for, 
372                         renew : true or false - whether or not this is a renewal
373                 @return The event that occurred during the permit check.  
374         /);
375
376 __PACKAGE__->register_method (
377         method          => 'permit_circ',
378         api_name                => 'open-ils.circ.checkout.permit.override',
379         signature       => q/@see open-ils.circ.checkout.permit/,
380 );
381
382 sub permit_circ {
383         my( $self, $client, $authtoken, $params ) = @_;
384         $U->logmark;
385
386         my $override = $params->{override} = 1 if $self->api_name =~ /override/o;
387
388         my ( $requestor, $patron, $ctx, $evt, $circ );
389
390         # check permisson of the requestor
391         ( $requestor, $patron, $evt ) = 
392                 $U->checkses_requestor( 
393                 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
394         return $evt if $evt;
395
396
397         # fetch and build the circulation environment
398         if( !( $ctx = $params->{_ctx}) ) {
399
400                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
401                         patron                                                  => $patron, 
402                         requestor                                               => $requestor, 
403                         type                                                            => 'circ',
404                         #fetch_patron_circ_summary      => 1,
405                         fetch_copy_statuses                     => 1, 
406                         fetch_copy_locations                    => 1, 
407                         );
408                 return $evt if $evt;
409         }
410
411         my $copy = $ctx->{copy};
412         if($copy) {
413                 my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
414                 return OpenILS::Event->new('COPY_IN_TRANSIT') 
415                         if $stat == $U->copy_status_from_name('in transit')->id;
416         }
417
418         $ctx->{authtoken} = $authtoken;
419
420         $evt = undef;
421         if( $ctx->{copy} and ($evt = _handle_claims_returned($ctx)) ) {
422                 return $evt unless $U->event_equals($evt, 'SUCCESS');
423         }
424
425         if($evt) { 
426                 $evt = undef;
427
428         } else { 
429
430                 # no claims returned circ was found, check if there is any open circ
431                 if( !$ctx->{ishold} and !$__isrenewal and $ctx->{copy} ) {
432                         ($circ, $evt) = $U->fetch_open_circulation($ctx->{copy}->id);
433                         return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
434                 }
435         }
436
437
438         $ctx->{permit_key} = _cache_permit_key();
439         my $events = _run_permit_scripts($ctx);
440
441         if( $override ) {
442                 $evt = override_events($requestor, $requestor->ws_ou, 
443                         $events, $authtoken, $ctx->{copy}->id, $client);
444                 return $evt if $evt;
445                 return OpenILS::Event->new('SUCCESS', payload => $ctx->{permit_key} );
446         }
447
448         return $events;
449 }
450
451 sub override_events {
452
453         my( $requestor, $org, $events, $authtoken, $copyid, $conn ) = @_;
454         $events = [ $events ] unless ref($events) eq 'ARRAY';
455         my @failed;
456
457         for my $e (@$events) {
458                 my $tc = $e->{textcode};
459                 next if $tc eq 'SUCCESS';
460                 my $ov = "$tc.override";
461                 $logger->info("attempting to override event $ov");
462                 my $evt = $U->check_perms( $requestor->id, $org, $ov );
463                 return $evt if $evt;
464         }
465
466         return undef;
467 }
468
469
470 __PACKAGE__->register_method(
471         method  => "check_title_hold",
472         api_name        => "open-ils.circ.title_hold.is_possible",
473         notes           => q/
474                 Determines if a hold were to be placed by a given user,
475                 whether or not said hold would have any potential copies
476                 to fulfill it.
477                 @param authtoken The login session key
478                 @param params A hash of named params including:
479                         patronid  - the id of the hold recipient
480                         titleid (brn) - the id of the title to be held
481                         depth   - the hold range depth (defaults to 0)
482         /);
483
484 # XXX add pickup lib to the call to test for perms
485
486 sub check_title_hold {
487         my( $self, $client, $authtoken, $params ) = @_;
488         my %params = %$params;
489         my $titleid = $params{titleid};
490
491         my ( $requestor, $patron, $evt ) = $U->checkses_requestor( 
492                 $authtoken, $params{patronid}, 'VIEW_HOLD_PERMIT' );
493         return $evt if $evt;
494
495         my $rangelib    = $patron->home_ou;
496         my $depth               = $params{depth} || 0;
497         my $pickup              = $params{pickup_lib};
498
499         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
500
501         my $org = $U->simplereq(
502                 'open-ils.actor', 
503                 'open-ils.actor.org_unit.retrieve', 
504                 $authtoken, $requestor->home_ou );
505
506         my $limit       = 10;
507         my $offset      = 0;
508         my $title;
509
510         while( $title = $U->storagereq(
511                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
512                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
513
514                 last unless 
515                         ref($title) and 
516                         ref($title->call_numbers) and 
517                         @{$title->call_numbers};
518
519                 for my $cn (@{$title->call_numbers}) {
520         
521                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
522         
523                         for my $copy (@{$cn->copies}) {
524         
525                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
526         
527                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
528                                         {       patron                          => $patron, 
529                                                 requestor                       => $requestor, 
530                                                 copy                                    => $copy,
531                                                 title                                   => $title, 
532                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
533                                                 pickup_lib                      => $pickup,
534                                                 request_lib                     => $org } );
535         
536                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
537                         }
538                 }
539
540                 $offset += $limit;
541         }
542
543         return 0;
544 }
545
546
547 # Runs the patron and copy permit scripts
548 # if this is a non-cat circulation, the copy permit script 
549 # is not run
550 sub _run_permit_scripts {
551
552         my $ctx                 = shift;
553         my $runner              = $ctx->{runner};
554         my $patronid    = $ctx->{patron}->id;
555         my $barcode             = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
556         my $key                 = $ctx->{permit_key};
557
558
559
560         # ---------------------------------------------------------------------
561         # Find all of the fatal penalties currently set on the user
562         # ---------------------------------------------------------------------
563         my $penalties = $U->update_patron_penalties( 
564                 authtoken => $ctx->{authtoken}, 
565                 patron    => $ctx->{patron} 
566         );
567
568         $penalties = $penalties->{fatal_penalties};
569         $logger->info("circ patron penalties user $patronid: @$penalties");
570
571
572         # ---------------------------------------------------------------------
573         # Now run the patron permit script 
574         # ---------------------------------------------------------------------
575         $runner->load($scripts{circ_permit_patron});
576         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
577
578         my $patron_events = $runner->retrieve('result.events');
579         $patron_events = [ split(/,/, $patron_events) ]; 
580         $ctx->{circ_permit_patron_events} = $patron_events;
581         $logger->activity("circ_permit_patron for returned @$patron_events") if @$patron_events;
582
583         my @evts_so_far = (@$penalties, @$patron_events);
584
585         return \@evts_so_far if @evts_so_far;
586
587
588         if( $ctx->{noncat} ) {
589                 $logger->debug("Exiting circ permit early because item is a non-cataloged item");
590                 return OpenILS::Event->new('SUCCESS', payload => $key);
591         }
592
593         if($ctx->{precat}) {
594                 $logger->debug("Exiting circ permit early because copy is pre-cataloged");
595                 return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
596         }
597
598         if($ctx->{ishold}) {
599                 $logger->debug("Exiting circ permit early because request is for hold patron permit");
600                 return OpenILS::Event->new('SUCCESS');
601         }
602
603
604
605         # ---------------------------------------------------------------------
606         # Capture all of the copy permit events
607         # ---------------------------------------------------------------------
608         $runner->load($scripts{circ_permit_copy});
609         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
610
611         my $copy_events = $runner->retrieve('result.events');
612         $copy_events = [ split(/,/, $copy_events) ]; 
613         $ctx->{circ_permit_copy_events} = $copy_events;
614         $logger->activity("circ_permit_copy for copy ".
615                 "$barcode returned events: @$copy_events") if @$copy_events;
616
617
618
619
620         # ---------------------------------------------------------------------
621         # Now collect all of the events together
622         # ---------------------------------------------------------------------
623         my @allevents = ( @evts_so_far, @$copy_events );
624
625         #push( @allevents, OpenILS::Event->new($_)) for @$penalties;
626         #push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
627
628         my $ae = _check_copy_alert($ctx->{copy});
629         push( @allevents, $ae ) if $ae;
630
631         return OpenILS::Event->new('SUCCESS', payload => $key) unless (@allevents);
632
633         # uniquify 
634         my %hash = map { ($_->{ilsevent} => $_) } @allevents;
635         @allevents = values %hash;
636
637         for (@allevents) {
638                 $_->{payload} = $ctx->{copy}->status->id
639                         if ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
640         }
641
642         return \@allevents;
643 }
644
645 sub _check_copy_alert {
646         my $copy = shift;
647         return OpenILS::Event->new('COPY_ALERT_MESSAGE', 
648                 payload => $copy->alert_message) if $copy->alert_message;
649         return undef;
650 }
651
652 # takes copyid, patronid, and requestor id
653 sub _cache_permit_key {
654         my $key = md5_hex( time() . rand() . "$$" );
655         $logger->debug("Setting circ permit key to $key");
656         $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
657         return $key;
658 }
659
660 sub _check_permit_key {
661         my $key = shift;
662         $logger->debug("Fetching circ permit key $key");
663         my $k = "oils_permit_key_$key";
664         my $one = $cache_handle->get_cache($k);
665         $cache_handle->delete_cache($k);
666         return ($one) ? 1 : 0;
667 }
668
669
670 # ------------------------------------------------------------------------------
671
672 __PACKAGE__->register_method(
673         method  => "checkout",
674         api_name        => "open-ils.circ.checkout",
675         notes => q/
676                 Checks out an item
677                 @param authtoken The login session key
678                 @param params A named hash of params including:
679                         copy                    The copy object
680                         barcode         If no copy is provided, the copy is retrieved via barcode
681                         copyid          If no copy or barcode is provide, the copy id will be use
682                         patron          The patron's id
683                         noncat          True if this is a circulation for a non-cataloted item
684                         noncat_type     The non-cataloged type id
685                         noncat_circ_lib The location for the noncat circ.  
686                         precat          The item has yet to be cataloged
687                         dummy_title The temporary title of the pre-cataloded item
688                         dummy_author The temporary authr of the pre-cataloded item
689                                 Default is the home org of the staff member
690                 @return The SUCCESS event on success, any other event depending on the error
691         /);
692
693 sub checkout {
694         my( $self, $client, $authtoken, $params ) = @_;
695         $U->logmark;
696
697         my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
698         my $key = $params->{permit_key};
699
700         # if this is a renewal, then the requestor does not have to
701         # have checkout privelages
702         ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
703         ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKOUT' ) unless $__isrenewal;
704         return $evt if $evt;
705
706         if( $params->{patron} ) {
707                 ( $patron, $evt ) = $U->fetch_user($params->{patron});
708                 return $evt if $evt;
709         } else {
710                 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
711                 return $evt if $evt;
712         }
713
714         # set the circ lib to the home org of the requestor if not specified
715         my $circlib = (defined($params->{circ_lib})) ? 
716                 $params->{circ_lib} : $requestor->ws_ou;
717
718
719         # Make sure the caller has a valid permit key or is 
720         # overriding the permit can
721         if( $params->{permit_override} ) {
722                 $evt = $U->check_perms(
723                         $requestor->id, $requestor->ws_ou, 'CIRC_PERMIT_OVERRIDE');
724                 return $evt if $evt;
725
726         } else {
727                 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
728                         unless _check_permit_key($key);
729         }
730
731         # if this is a non-cataloged item, check it out and return
732         return _checkout_noncat( 
733                 $key, $requestor, $patron, %$params ) if $params->{noncat};
734
735         # if this item has yet to be cataloged, make sure a dummy copy exists
736         ( $params->{copy}, $evt ) = _make_precat_copy(
737                 $requestor, $circlib, $params ) if $params->{precat};
738         return $evt if $evt;
739
740
741         # fetch and build the circulation environment
742         if( !( $ctx = $params->{_ctx}) ) {
743                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
744                         patron                                                  => $patron, 
745                         requestor                                               => $requestor, 
746                         session                                                 => $U->start_db_session(),
747                         type                                                            => 'circ',
748                         #fetch_patron_circ_summary      => 1,
749                         fetch_copy_statuses                     => 1, 
750                         fetch_copy_locations                    => 1, 
751                         );
752                 return $evt if $evt;
753         }
754         $ctx->{session} = $U->start_db_session() unless $ctx->{session};
755
756         # if the call doesn't know it's not cataloged..
757         if(!$params->{precat}) {
758                 if( $ctx->{copy}->call_number eq '-1' ) {
759                         return OpenILS::Event->new('ITEM_NOT_CATALOGED');
760                 }
761         }
762
763
764         $copy = $ctx->{copy};
765         if($copy) {
766                 my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
767                 return OpenILS::Event->new('COPY_IN_TRANSIT') 
768                         if $stat == $U->copy_status_from_name('in transit')->id;
769         }
770
771         # this happens in permit.. but we need to check here for 'offline' requests
772         ($circ) = $U->fetch_open_circulation($ctx->{copy}->id);
773         return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
774
775         my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
776
777
778         $ctx->{circ_lib} = $circlib;
779
780         $evt = _run_checkout_scripts($ctx);
781         return $evt if $evt;
782
783
784         _build_checkout_circ_object($ctx);
785
786         $evt = _apply_modified_due_date($ctx);
787         return $evt if $evt;
788
789         $evt = _commit_checkout_circ_object($ctx);
790         return $evt if $evt;
791
792         $evt = _update_checkout_copy($ctx);
793         return $evt if $evt;
794
795         my $holds;
796         ($holds, $evt) = _handle_related_holds($ctx);
797         return $evt if $evt;
798
799
800         $logger->debug("Checkout committing objects with session thread trace: ".$ctx->{session}->session_id);
801         $U->commit_db_session($ctx->{session});
802         my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
803
804         $logger->activity("user ".$requestor->id." successfully checked out item ".
805                 $ctx->{copy}->barcode." to user ".$ctx->{patron}->id );
806
807
808         # ------------------------------------------------------------------------------
809         # Update the patron penalty info in the DB
810         # ------------------------------------------------------------------------------
811         $U->update_patron_penalties( 
812                 authtoken => $authtoken, 
813                 patron    => $ctx->{patron} ,
814                 background      => 1,
815         );
816
817         return OpenILS::Event->new('SUCCESS', 
818                 payload => { 
819                         copy                                    => $U->unflesh_copy($ctx->{copy}),
820                         circ                                    => $ctx->{circ},
821                         record                          => $record,
822                         holds_fulfilled => $holds,
823                 } 
824         )
825 }
826
827
828 sub _make_precat_copy {
829         my ( $requestor, $circlib, $params ) =  @_;
830         $U->logmark;
831         my( $copy, undef ) = _find_copy_by_attr(%$params);
832
833         if($copy) {
834                 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
835
836                 $copy->editor($requestor->id);
837                 $copy->edit_date('now');
838                 $copy->dummy_title($params->{dummy_title});
839                 $copy->dummy_author($params->{dummy_author});
840
841                 my $stat = $U->storagereq(
842                         'open-ils.storage.direct.asset.copy.update', $copy );
843
844                 return (undef, $U->DB_UPDATE_FAILED($copy)) unless $stat;
845                 return ($copy);
846         }
847
848         $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
849
850         my $evt = OpenILS::Event->new(
851                 'BAD_PARAMS', desc => "Dummy title or author not provided" ) 
852                 unless ( $params->{dummy_title} and $params->{dummy_author} );
853         return (undef, $evt) if $evt;
854
855         $copy = Fieldmapper::asset::copy->new;
856         $copy->circ_lib($circlib);
857         $copy->creator($requestor->id);
858         $copy->editor($requestor->id);
859         $copy->barcode($params->{barcode});
860         $copy->call_number(-1); #special CN for precat materials
861         $copy->loan_duration(&PRECAT_LOAN_DURATION); 
862         $copy->fine_level(&PRECAT_FINE_LEVEL);
863
864         $copy->dummy_title($params->{dummy_title});
865         $copy->dummy_author($params->{dummy_author});
866
867         my $id = $U->storagereq(
868                 'open-ils.storage.direct.asset.copy.create', $copy );
869         return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
870
871         $logger->debug("Pre-cataloged copy successfully created");
872         return ($U->fetch_copy($id));
873 }
874
875
876 sub _run_checkout_scripts {
877         my $ctx = shift;
878         $U->logmark;
879         my $evt;
880         my $circ;
881
882         my $runner = $ctx->{runner};
883
884         $runner->insert('result.durationLevel');
885         $runner->insert('result.durationRule');
886         $runner->insert('result.recurringFinesRule');
887         $runner->insert('result.recurringFinesLevel');
888         $runner->insert('result.maxFine');
889
890         $runner->load($scripts{circ_duration});
891         $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
892         my $duration = $runner->retrieve('result.durationRule');
893         $logger->debug("Circ duration script yielded a duration rule of: $duration");
894
895         $runner->load($scripts{circ_recurring_fines});
896         $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
897         my $recurring = $runner->retrieve('result.recurringFinesRule');
898         $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
899
900         $runner->load($scripts{circ_max_fines});
901         $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
902         my $max_fine = $runner->retrieve('result.maxFine');
903         $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
904
905         ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
906         return $evt if $evt;
907         ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
908         return $evt if $evt;
909         ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
910         return $evt if $evt;
911
912         $ctx->{duration_level}                  = $runner->retrieve('result.durationLevel');
913         $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
914         $ctx->{duration_rule}                   = $duration;
915         $ctx->{recurring_fines_rule}    = $recurring;
916         $ctx->{max_fine_rule}                   = $max_fine;
917
918         return undef;
919 }
920
921 sub _build_checkout_circ_object {
922         my $ctx = shift;
923         $U->logmark;
924
925         my $circ                        = new Fieldmapper::action::circulation;
926         my $duration    = $ctx->{duration_rule};
927         my $max                 = $ctx->{max_fine_rule};
928         my $recurring   = $ctx->{recurring_fines_rule};
929         my $copy                        = $ctx->{copy};
930         my $patron              = $ctx->{patron};
931         my $dur_level   = $ctx->{duration_level};
932         my $rec_level   = $ctx->{recurring_fines_level};
933
934         $circ->duration( $duration->shrt ) if ($dur_level == 1);
935         $circ->duration( $duration->normal ) if ($dur_level == 2);
936         $circ->duration( $duration->extended ) if ($dur_level == 3);
937
938         $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
939         $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
940         $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
941
942         $circ->duration_rule( $duration->name );
943         $circ->recuring_fine_rule( $recurring->name );
944         $circ->max_fine_rule( $max->name );
945         $circ->max_fine( $max->amount );
946
947         $circ->fine_interval($recurring->recurance_interval);
948         $circ->renewal_remaining( $duration->max_renewals );
949         $circ->target_copy( $copy->id );
950         $circ->usr( $patron->id );
951         $circ->circ_lib( $ctx->{circ_lib} );
952
953         if( $__isrenewal ) {
954                 $logger->debug("Circ is a renewal.  Setting renewal_remaining to " . $ctx->{renewal_remaining} );
955                 $circ->opac_renewal(1); 
956                 $circ->renewal_remaining($ctx->{renewal_remaining});
957                 $circ->circ_staff($ctx->{requestor}->id);
958         } 
959
960
961         # if the user provided an overiding checkout time, 
962         # (e.g. the checkout really happened several hours ago), then
963         # we apply that here.  Does this need a perm??
964         if( my $ds = _create_date_stamp($ctx->{checkout_time}) ) {
965                 $logger->debug("circ setting checkout_time to $ds");
966                 $circ->xact_start($ds);
967         }
968
969         # if a patron is renewing, 'requestor' will be the patron
970         $circ->circ_staff($ctx->{requestor}->id ); 
971         _set_circ_due_date($circ);
972         $ctx->{circ} = $circ;
973 }
974
975 sub _apply_modified_due_date {
976         my $ctx = shift;
977         my $circ = $ctx->{circ};
978
979         $U->logmark;
980
981         if( $ctx->{due_date} ) {
982
983                 my $evt = $U->check_perms(
984                         $ctx->{requestor}->id, $ctx->{circ_lib}, 'CIRC_OVERRIDE_DUE_DATE');
985                 return $evt if $evt;
986
987                 my $ds = _create_date_stamp($ctx->{due_date});
988                 $logger->debug("circ modifying  due_date to $ds");
989                 $circ->due_date($ds);
990
991         } else {
992
993                 # if the due_date lands on a day when the location is closed
994                 my $copy = $ctx->{copy};
995                 return unless $copy;
996
997                 $logger->info("circ searching for closed date overlap on lib ".
998                         $copy->circ_lib->id ." with an item due date of ".$circ->due_date );
999
1000                 my $dateinfo = $ctx->{session}->request(
1001                         'open-ils.storage.actor.org_unit.closed_date.overlap',
1002                         $copy->circ_lib->id, $circ->due_date )->gather(1);
1003
1004
1005                 if($dateinfo) {
1006                         $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
1007                                 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1008
1009                                 # XXX make the behavior more dynamic
1010                                 # for now, we just push the due date to after the close date
1011                                 $circ->due_date($dateinfo->{end});
1012                 }
1013
1014         }
1015         return undef;
1016 }
1017
1018 sub _create_date_stamp {
1019         my $datestring = shift;
1020         return undef unless $datestring;
1021         $datestring = clense_ISO8601($datestring);
1022         $logger->debug("circ created date stamp => $datestring");
1023         return $datestring;
1024 }
1025
1026 sub _create_due_date {
1027         my $duration = shift;
1028         $U->logmark;
1029         my ($sec,$min,$hour,$mday,$mon,$year) = 
1030                 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1031         $year += 1900; $mon += 1;
1032         my $due_date = sprintf(
1033         '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1034         $year, $mon, $mday, $hour, $min, $sec);
1035         return $due_date;
1036 }
1037
1038 sub _set_circ_due_date {
1039         my $circ = shift;
1040         $U->logmark;
1041         my $dd = _create_due_date($circ->duration);
1042         $logger->debug("Checkout setting due date on circ to: $dd");
1043         $circ->due_date($dd);
1044 }
1045
1046 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
1047 sub _update_checkout_copy {
1048         my $ctx = shift;
1049         $U->logmark;
1050         my $copy = $ctx->{copy};
1051
1052         my $s = $U->copy_status_from_name('checked out');
1053         $copy->status( $s->id ) if $s;
1054
1055         my $evt = $U->update_copy( session => $ctx->{session}, 
1056                 copy => $copy, editor => $ctx->{requestor}->id );
1057         return (undef,$evt) if $evt;
1058
1059         return undef;
1060 }
1061
1062 # commits the circ object to the db then fleshes the circ with rules objects
1063 sub _commit_checkout_circ_object {
1064
1065         my $ctx = shift;
1066         my $circ = $ctx->{circ};
1067         $U->logmark;
1068
1069         $circ->clear_id;
1070         my $r = $ctx->{session}->request(
1071                 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
1072
1073         return $U->DB_UPDATE_FAILED($circ) unless $r;
1074
1075         $logger->debug("Created a new circ object in checkout: $r");
1076
1077         $circ->id($r);
1078         $circ->duration_rule($ctx->{duration_rule});
1079         $circ->max_fine_rule($ctx->{max_fine_rule});
1080         $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
1081
1082         return undef;
1083 }
1084
1085
1086 # sees if there are any holds that this copy 
1087 sub _handle_related_holds {
1088
1089         my $ctx         = shift;
1090         my $copy                = $ctx->{copy};
1091         my $patron      = $ctx->{patron};
1092         my $holds       = $holdcode->fetch_related_holds($copy->id);
1093         $U->logmark;
1094         my @fulfilled;
1095
1096         # XXX We should only fulfill one hold here...
1097         # XXX If a hold was transited to the user who is checking out
1098         # the item, we need to make sure that hold is what's grabbed
1099         if(ref($holds) && @$holds) {
1100
1101                 # for now, just sort by id to get what should be the oldest hold
1102                 $holds = [ sort { $a->id <=> $b->id } @$holds ];
1103                 my @myholds = grep { $_->usr eq $patron->id } @$holds;
1104                 my @altholds    = grep { $_->usr ne $patron->id } @$holds;
1105
1106                 if(@myholds) {
1107                         my $hold = $myholds[0];
1108
1109                         $logger->debug("Related hold found in checkout: " . $hold->id );
1110
1111                         $hold->current_copy($copy->id); # just make sure it's set
1112                         # if the hold was never officially captured, capture it.
1113                         $hold->capture_time('now') unless $hold->capture_time;
1114                         $hold->fulfillment_time('now');
1115                         my $r = $ctx->{session}->request(
1116                                 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
1117                         return (undef,$U->DB_UPDATE_FAILED( $hold )) unless $r;
1118                         push( @fulfilled, $hold->id );
1119                 }
1120
1121                 # If there are any holds placed for other users that point to this copy,
1122                 # then we need to un-target those holds so the targeter can pick a new copy
1123                 for(@altholds) {
1124
1125                         $logger->info("Un-targeting hold ".$_->id.
1126                                 " because copy ".$copy->id." is getting checked out");
1127
1128                         $_->clear_current_copy;
1129                         my $r = $ctx->{session}->request(
1130                                 "open-ils.storage.direct.action.hold_request.update", $_ )->gather(1);
1131                         return (undef,$U->DB_UPDATE_FAILED( $_ )) unless $r;
1132                 }
1133         }
1134
1135         return (\@fulfilled, undef);
1136 }
1137
1138 sub _checkout_noncat {
1139         my ( $key, $requestor, $patron, %params ) = @_;
1140         my( $circ, $circlib, $evt );
1141         $U->logmark;
1142
1143         $circlib = $params{noncat_circ_lib} || $requestor->ws_ou;
1144
1145         my $count = $params{noncat_count} || 1;
1146         my $cotime = _create_date_stamp($params{checkout_time}) || "";
1147         $logger->info("circ creating $count noncat circs with checkout time $cotime");
1148         for(1..$count) {
1149                 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1150                         $requestor->id, $patron->id, $circlib, $params{noncat_type}, $cotime );
1151                 return $evt if $evt;
1152         }
1153
1154         return OpenILS::Event->new( 
1155                 'SUCCESS', payload => { noncat_circ => $circ } );
1156 }
1157
1158
1159 __PACKAGE__->register_method(
1160         method  => "generic_receive",
1161         api_name        => "open-ils.circ.checkin",
1162         argc            => 2,
1163         signature       => q/
1164                 Generic super-method for handling all copies
1165                 @param authtoken The login session key
1166                 @param params Hash of named parameters including:
1167                         barcode - The copy barcode
1168                         force           - If true, copies in bad statuses will be checked in and give good statuses
1169                         ...
1170         /
1171 );
1172
1173 __PACKAGE__->register_method(
1174         method  => "generic_receive",
1175         api_name        => "open-ils.circ.checkin.override",
1176         signature       => q/@see open-ils.circ.checkin/
1177 );
1178
1179 sub generic_receive {
1180         my( $self, $conn, $authtoken, $params ) = @_;
1181         my( $ctx, $requestor, $evt );
1182
1183         ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
1184         ( $requestor, $evt ) = $U->checksesperm( 
1185                 $authtoken, 'COPY_CHECKIN' ) unless $__isrenewal;
1186         return $evt if $evt;
1187
1188         # load up the circ objects
1189         if( !( $ctx = $params->{_ctx}) ) {
1190                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
1191                         requestor                                               => $requestor, 
1192                         session                                                 => $U->start_db_session(),
1193                         type                                                            => 'circ',
1194                         fetch_copy_statuses                     => 1, 
1195                         fetch_copy_locations                    => 1, 
1196                         no_runner                                               => 1,  
1197                         );
1198                 return $evt if $evt;
1199         }
1200         $ctx->{override} = 1 if $self->api_name =~ /override/o;
1201         $ctx->{session} = $U->start_db_session() unless $ctx->{session};
1202         $ctx->{authtoken} = $authtoken;
1203         my $session = $ctx->{session};
1204
1205         my $copy = $ctx->{copy};
1206         $U->unflesh_copy($copy);
1207         return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') unless $copy;
1208
1209         $logger->info("Checkin copy called by user ".
1210                 $requestor->id." for copy ".$copy->id);
1211
1212         # ------------------------------------------------------------------------------
1213         # Update the patron penalty info in the DB
1214         # ------------------------------------------------------------------------------
1215         $U->update_patron_penalties( 
1216                 authtoken => $authtoken, 
1217                 patron    => $ctx->{patron},
1218                 background => 1
1219         );
1220
1221         return $self->checkin_do_receive($conn, $ctx);
1222 }
1223
1224 sub checkin_do_receive {
1225
1226         my( $self, $connection, $ctx ) = @_;
1227
1228         my $evt;
1229         my $copy                        = $ctx->{copy};
1230         my $session             = $ctx->{session};
1231         my $requestor   = $ctx->{requestor};
1232         my $change              = 0; # did we actually do anything?
1233         my $circ;
1234
1235         my @eventlist;
1236
1237         # does the copy have an attached alert message?
1238         my $ae = _check_copy_alert($copy);
1239         push(@eventlist, $ae) if $ae;
1240
1241         # is the copy is an a status we can't automatically resolve?
1242         $evt = _checkin_check_copy_status($ctx);
1243         push( @eventlist, $evt ) if $evt;
1244
1245
1246         # - see if the copy has an open circ attached
1247         ($ctx->{circ}, $evt)    = $U->fetch_open_circulation($copy->id);
1248         return $evt if ($evt and $__isrenewal); # renewals require a circulation
1249         $evt = undef;
1250         $circ = $ctx->{circ};
1251
1252         # if the circ is marked as 'claims returned', add the event to the list
1253         push( @eventlist, 'CIRC_CLAIMS_RETURNED' ) 
1254                 if ($circ and $circ->stop_fines and $circ->stop_fines eq 'CLAIMSRETURNED');
1255
1256         # override or die
1257         if(@eventlist) {
1258                 if($ctx->{override}) {
1259                         $evt = override_events($requestor, $requestor->ws_ou, \@eventlist );
1260                         return $evt if $evt;
1261                 } else {
1262                         return \@eventlist;
1263                 }
1264         }
1265
1266         ($ctx->{transit})       = $U->fetch_open_transit_by_copy($copy->id);
1267
1268         if( $ctx->{circ} ) {
1269
1270                 # There is an open circ on this item, close it out.
1271                 $change = 1;
1272                 $evt            = _checkin_handle_circ($ctx);
1273                 return $evt if $evt;
1274
1275         } elsif( $ctx->{transit} ) {
1276
1277                 # is this item currently in transit?
1278                 $change                 = 1;
1279                 $evt                            = $transcode->transit_receive( $copy, $requestor, $session );
1280                 my $holdtrans   = $evt->{holdtransit};
1281                 ($ctx->{hold})  = $U->fetch_hold($holdtrans->hold) if $holdtrans;
1282
1283                 if( ! $U->event_equals($evt, 'SUCCESS') ) {
1284
1285                         # either an error occurred or a ROUTE_ITEM was generated and the 
1286                         # item must be forwarded on to its destination.
1287                         return _checkin_flesh_event($ctx, $evt);
1288
1289                 } else {
1290
1291                         # Transit has been closed, now let's see if the copy's original
1292                         # status is something the staff should be warned of
1293                         my $e = _checkin_check_copy_status($ctx);
1294                         $evt = $e if $e;
1295
1296                         if($holdtrans) {
1297
1298                                 # copy was received as a hold transit.  Copy is at target lib
1299                                 # and hold transit is complete.  We're done here...
1300                                 $U->commit_db_session($session);
1301                                 return _checkin_flesh_event($ctx, $evt);
1302                         }
1303                         $evt = undef;
1304                 }
1305         }
1306
1307         # ------------------------------------------------------------------------------
1308         # Circulations and transits are now closed where necessary.  Now go on to see if
1309         # this copy can fulfill a hold or needs to be routed to a different location
1310         # ------------------------------------------------------------------------------
1311
1312
1313         # If it's a renewal, we're done
1314         if($__isrenewal) {
1315                 #$$ctx{force} = 1;
1316                 #my ($cc, $ee) = _reshelve_copy($ctx);
1317                 #return $ee if $ee;
1318                 #delete $$ctx{force};
1319                 $U->commit_db_session($session);
1320                 return OpenILS::Event->new('SUCCESS');
1321         }
1322
1323         # Now, let's see if this copy is needed for a hold
1324         my ($hold) = $holdcode->find_local_hold( $session, $copy, $requestor ); 
1325
1326         if($hold) {
1327
1328                 $ctx->{hold}    = $hold;
1329                 $change                 = 1;
1330                 
1331                 # Capture the hold with this copy
1332                 return $evt if ($evt = _checkin_capture_hold($ctx));
1333
1334                 if( $hold->pickup_lib == $requestor->ws_ou ) {
1335
1336                         # This hold was captured in the correct location
1337                         $evt = OpenILS::Event->new('SUCCESS');
1338
1339                 } else {
1340
1341                         # Hold needs to be picked up elsewhere.  Build a hold 
1342                         # transit and route the item.
1343                         return $evt if ($evt =_checkin_build_hold_transit($ctx));
1344                         $evt = OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib);
1345                 }
1346
1347         } else { # not needed for a hold
1348
1349                 if( $copy->circ_lib == $requestor->ws_ou ) {
1350
1351                         # Copy is in the right place.
1352                         $evt = OpenILS::Event->new('SUCCESS');
1353
1354                         # if the item happens to be a pre-cataloged item, send it
1355                         # to cataloging and return the event
1356                         my( $e, $c, $err ) = _checkin_handle_precat($ctx);
1357                         return $err if $err;
1358                         $change         = 1 if $c;
1359                         $evt                    = $e if $e;
1360
1361                 } else {
1362
1363                         # Copy wants to go home. Transit it there.
1364                         return $evt if ( $evt = _checkin_build_generic_copy_transit($ctx) );
1365                         $evt                    = OpenILS::Event->new('ROUTE_ITEM', org => $copy->circ_lib);
1366                         $change         = 1;
1367                 }
1368         }
1369
1370
1371         # ------------------------------------------------------------------
1372         # if the copy is not in a state that should persist,
1373         # set the copy to reshelving if it's not already there
1374         # ------------------------------------------------------------------
1375         my ($c, $e) = _reshelve_copy($ctx);
1376         return $e if $e;
1377         $change = $c unless $change;
1378
1379         if(!$change) {
1380
1381                 $evt = OpenILS::Event->new('NO_CHANGE');
1382                 ($ctx->{hold}) = $U->fetch_open_hold_by_copy($copy->id) 
1383
1384                 # what is this?
1385                 if( $copy->status == $U->copy_status_from_name('on holds shelf')->id );
1386
1387         } else {
1388
1389                 $U->commit_db_session($session);
1390         }
1391
1392         $logger->activity("checkin by user ".$requestor->id." on item ".
1393                 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1394
1395         return _checkin_flesh_event($ctx, $evt);
1396 }
1397
1398 sub _reshelve_copy {
1399
1400         my $ctx = shift;
1401         my $copy                = $ctx->{copy};
1402         my $reqr                = $ctx->{requestor};
1403         my $session     = $ctx->{session};
1404         my $force       = $ctx->{force};
1405
1406         my $stat = ref($copy->status) ? $copy->status->id : $copy->status;
1407
1408         if($force || (
1409                 $stat != $U->copy_status_from_name('on holds shelf')->id and 
1410                 $stat != $U->copy_status_from_name('available')->id and 
1411                 $stat != $U->copy_status_from_name('cataloging')->id and 
1412                 $stat != $U->copy_status_from_name('in transit')->id and 
1413                 $stat != $U->copy_status_from_name('reshelving')->id) ) {
1414
1415                 $copy->status( $U->copy_status_from_name('reshelving')->id );
1416
1417                 my $evt = $U->update_copy( 
1418                         copy            => $copy,
1419                         editor  => $reqr->id,
1420                         session => $session,
1421                         );
1422
1423                 return( 1, $evt );
1424         }
1425         return undef;
1426 }
1427
1428
1429
1430
1431 # returns undef if there are no 'open' claims-returned circs attached
1432 # to the given copy.  if there is an open claims-returned circ, 
1433 # then we check for override mode.  if in override, mark the claims-returned
1434 # circ as checked in.  if not, return event.
1435 sub _handle_claims_returned {
1436         my $ctx = shift;
1437         my $copy = $ctx->{copy};
1438
1439         my $CR  = _fetch_open_claims_returned($copy->id);
1440         return undef unless $CR;
1441
1442         # - If the caller has set the override flag, we will check the item in
1443         if($ctx->{override}) {
1444
1445                 $CR->checkin_time('now');       
1446                 $CR->checkin_lib($ctx->{requestor}->ws_ou);
1447                 $CR->checkin_staff($ctx->{requestor}->id);
1448
1449                 my $stat = $U->storagereq(
1450                         'open-ils.storage.direct.action.circulation.update', $CR);
1451                 return $U->DB_UPDATE_FAILED($CR) unless $stat;
1452                 return OpenILS::Event->new('SUCCESS');
1453
1454         } else {
1455                 # - if not in override mode, return the CR event
1456                 return OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
1457         }
1458 }
1459
1460
1461 sub _fetch_open_claims_returned {
1462         my $copyid = shift;
1463         my $trans = $U->storagereq(
1464                 'open-ils.storage.direct.action.circulation.search_where',
1465                 {       
1466                         target_copy             => $copyid, 
1467                         stop_fines              => 'CLAIMSRETURNED',
1468                         checkin_time    => undef,
1469                 }
1470         );
1471         return $$trans[0] if $trans && $$trans[0];
1472         return undef;
1473 }
1474
1475 # - if the copy is has the 'in process' status, set it to reshelving
1476 #sub _check_in_process {
1477         #my $ctx = shift;
1478 #
1479         #my $copy = $ctx->{copy};
1480         #my $reqr       = $ctx->{requestor};
1481         #my $ses        = $ctx->{session};
1482 ##
1483         #my $stat = $U->copy_status_from_name('in process');
1484         #my $rstat = $U->copy_status_from_name('reshelving');
1485 #
1486         #if( $stat->id == $copy->status->id ) {
1487                 #$logger->info("marking 'in-process' copy ".$copy->id." as 'reshelving'");
1488                 #$copy->status( $rstat->id );
1489                 #my $evt = $U->update_copy( 
1490                         #copy           => $copy,
1491                         #editor => $reqr->id,
1492                         #session        => $ses
1493                         #);
1494                 #return $evt if $evt;
1495 #
1496                 #$copy->status( $rstat ); # - reflesh the copy status
1497         #}
1498         #return undef;
1499 #}
1500
1501
1502 # returns (ITEM_NOT_CATALOGED, change_occurred, $error_event) where necessary
1503 sub _checkin_handle_precat {
1504
1505         my $ctx         = shift;
1506         my $copy                = $ctx->{copy};
1507         my $evt         = undef;
1508         my $errevt      = undef;
1509         my $change      = 0;
1510
1511         my $catstat = $U->copy_status_from_name('cataloging');
1512
1513         if( $ctx->{precat} ) {
1514
1515                 $evt = OpenILS::Event->new('ITEM_NOT_CATALOGED');
1516
1517                 if( $copy->status != $catstat->id ) {
1518                         $copy->status($catstat->id);
1519
1520                         return (undef, 0, $errevt) if (
1521                                 $errevt = $U->update_copy(
1522                                         copy            => $copy, 
1523                                         editor  => $ctx->{requestor}->id, 
1524                                         session => $ctx->{session} ));
1525                         $change = 1;
1526
1527                 }
1528         }
1529
1530         return ($evt, $change, undef);
1531 }
1532
1533
1534 # returns the appropriate event for the given copy status
1535 # if the copy is not in a 'special' status, undef is returned
1536 sub _checkin_check_copy_status {
1537         my $ctx = shift;
1538         my $copy = $ctx->{copy};
1539         my $reqr        = $ctx->{requestor};
1540         my $ses = $ctx->{session};
1541
1542         my $islost              = 0;
1543         my $ismissing   = 0;
1544         my $evt                 = undef;
1545
1546         my $status = ref($copy->status) ? $copy->status->id : $copy->status;
1547
1548         return undef 
1549                 if(     $status == $U->copy_status_from_name('available')->id           ||
1550                                 $status == $U->copy_status_from_name('checked out')->id ||
1551                                 $status == $U->copy_status_from_name('in process')->id  ||
1552                                 $status == $U->copy_status_from_name('in transit')->id  ||
1553                                 $status == $U->copy_status_from_name('reshelving')->id );
1554
1555         return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy ) 
1556                 if( $status == $U->copy_status_from_name('lost')->id );
1557
1558         return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy ) 
1559                 if( $status == $U->copy_status_from_name('missing')->id );
1560
1561         return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1562
1563
1564
1565 #       my $rstat = $U->copy_status_from_name('reshelving');
1566 #       my $stat = (ref($copy->status)) ? $copy->status->id : $copy->status;
1567 #
1568 #       if( $stat == $U->copy_status_from_name('lost')->id ) {
1569 #               $islost = 1;
1570 #               $evt = OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy );
1571 #
1572 #       } elsif( $stat == $U->copy_status_from_name('missing')->id) {
1573 #               $ismissing = 1;
1574 #               $evt = OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy );
1575 #       }
1576 #
1577 #       return (undef,$evt) if(!$ctx->{override});
1578 #
1579 #       # we're are now going to attempt to override the failure 
1580 #       # and set the copy to reshelving
1581 #       my $e;
1582 #       my $copyid = $copy->id;
1583 #       my $userid = $reqr->id;
1584 #       if( $islost ) {
1585 #
1586 #               # - make sure we have permission
1587 #               $e = $U->check_perms( $reqr->id, 
1588 #                       $copy->circ_lib, 'COPY_STATUS_LOST.override');
1589 #               return (undef,$e) if $e;
1590 #               $copy->status( $rstat->id );
1591 #
1592 #               # XXX if no fines are owed in the circ, close it out - will this happen later anyway?
1593 #               #my $circ = $U->storagereq(
1594 #               #       'open-ils.storage.direct.action.circulation
1595 #
1596 #               $logger->activity("user $userid overriding 'lost' copy status for copy $copyid");
1597 #
1598 #       } elsif( $ismissing ) {
1599 #
1600 #               # - make sure we have permission
1601 #               $e = $U->check_perms( $reqr->id, 
1602 #                       $copy->circ_lib, 'COPY_STATUS_MISSING.override');
1603 #               return (undef,$e) if $e;
1604 #               $copy->status( $rstat->id );
1605 #               $logger->activity("user $userid overriding 'missing' copy status for copy $copyid");
1606 #       }
1607 #
1608 #       if( $islost or $ismissing ) {
1609 #
1610 #               # - update the copy with the new status
1611 #               $evt = $U->update_copy(
1612 #                       copy            => $copy,
1613 #                       editor  => $reqr->id,
1614 #                       session => $ses
1615 #               );
1616 #               return (undef,$evt) if $evt;
1617 #               $copy->status( $rstat );
1618 #       }
1619 #
1620 #       return (1);
1621
1622
1623 }
1624
1625 # Just gets the copy back home.  Returns undef on success, event on error
1626 sub _checkin_build_generic_copy_transit {
1627
1628         my $ctx                 = shift;
1629         my $requestor   = $ctx->{requestor};
1630         my $copy                        = $ctx->{copy};
1631         my $transit             = Fieldmapper::action::transit_copy->new;
1632         my $session             = $ctx->{session};
1633
1634         $logger->activity("User ". $requestor->id ." creating a ".
1635                 " new copy transit for copy ".$copy->id." to org ".$copy->circ_lib);
1636
1637         $transit->source($requestor->ws_ou);
1638         $transit->dest($copy->circ_lib);
1639         $transit->target_copy($copy->id);
1640         $transit->source_send_time('now');
1641         $transit->copy_status($copy->status);
1642         
1643         $logger->debug("Creating new copy_transit in DB");
1644
1645         my $s = $session->request(
1646                 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1647         return $U->DB_UPDATE_FAILED($transit) unless $s;
1648
1649         $logger->info("Checkin copy successfully created new transit: $s");
1650
1651         $copy->status($U->copy_status_from_name('in transit')->id );
1652
1653         return $U->update_copy( copy => $copy, 
1654                         editor => $requestor->id, session => $session );
1655         
1656 }
1657
1658
1659 # returns event on error, undef on success
1660 sub _checkin_build_hold_transit {
1661         my $ctx = shift;
1662
1663         my $copy = $ctx->{copy};
1664         my $hold = $ctx->{hold};
1665         my $trans = Fieldmapper::action::hold_transit_copy->new;
1666
1667         $trans->hold($hold->id);
1668         $trans->source($ctx->{requestor}->ws_ou);
1669         $trans->dest($hold->pickup_lib);
1670         $trans->source_send_time("now");
1671         $trans->target_copy($copy->id);
1672         $trans->copy_status($copy->status);
1673
1674         my $id = $ctx->{session}->request(
1675                 "open-ils.storage.direct.action.hold_transit_copy.create", $trans )->gather(1);
1676         return $U->DB_UPDATE_FAILED($trans) unless $id;
1677
1678         $logger->info("Checkin copy successfully created hold transit: $id");
1679
1680         $copy->status($U->copy_status_from_name('in transit')->id );
1681         return $U->update_copy( copy => $copy, 
1682                         editor => $ctx->{requestor}->id, session => $ctx->{session} );
1683 }
1684
1685 # Returns event on error, undef on success
1686 sub _checkin_capture_hold {
1687         my $ctx = shift;
1688         my $copy = $ctx->{copy};
1689         my $hold = $ctx->{hold}; 
1690
1691         $logger->debug("Checkin copy capturing hold ".$hold->id);
1692
1693         $hold->current_copy($copy->id);
1694         $hold->capture_time('now'); 
1695
1696         my $stat = $ctx->{session}->request(
1697                 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
1698         return $U->DB_UPDATE_FAILED($hold) unless $stat;
1699
1700         $copy->status( $U->copy_status_from_name('on holds shelf')->id );
1701
1702         return $U->update_copy( copy => $copy, 
1703                         editor => $ctx->{requestor}->id, session => $ctx->{session} );
1704 }
1705
1706 # fleshes an event with the relevant objects from the context
1707 sub _checkin_flesh_event {
1708         my $ctx = shift;
1709         my $evt = shift;
1710
1711         my $payload                             = {};
1712         $payload->{copy}                = $U->unflesh_copy($ctx->{copy});
1713         $payload->{record}      = $U->record_to_mvr($ctx->{title}) if($ctx->{title} and !$ctx->{precat});
1714         $payload->{circ}                = $ctx->{circ} if $ctx->{circ};
1715         $payload->{transit}     = $ctx->{transit} if $ctx->{transit};
1716         $payload->{hold}                = $ctx->{hold} if $ctx->{hold};
1717
1718         $evt->{payload} = $payload;
1719         return $evt;
1720 }
1721
1722
1723 # Closes out the circulation, puts the copy into reshelving.
1724 # Voids any bills attached to this circ after the backdate time 
1725 # if a backdate is provided
1726 sub _checkin_handle_circ { 
1727
1728         my $ctx = shift;
1729
1730         my $circ = $ctx->{circ};
1731         my $copy = $ctx->{copy};
1732         my $requestor   = $ctx->{requestor};
1733         my $session             = $ctx->{session};
1734         my $evt;
1735         my $obt;
1736
1737         $logger->info("Handling circulation [".$circ->id."] found in checkin...");
1738
1739         # backdate the circ if necessary
1740         if(my $backdate = $ctx->{backdate}) {
1741                 return $evt if ($evt = 
1742                         _checkin_handle_backdate($backdate, $circ, $requestor, $session, 1));
1743         }
1744
1745
1746         if(!$circ->stop_fines) {
1747                 $circ->stop_fines('CHECKIN');
1748                 $circ->stop_fines('RENEW') if $__isrenewal;
1749                 $circ->stop_fines_time('now');
1750         }
1751
1752         # see if there are any fines owed on this circ.  if not, close it
1753         ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1754         return $evt if $evt;
1755         $circ->xact_finish('now') if( $obt->balance_owed != 0 );
1756
1757         # Set the checkin vars since we have the item
1758         $circ->checkin_time('now');
1759         $circ->checkin_staff($requestor->id);
1760         $circ->checkin_lib($requestor->ws_ou);
1761
1762         $evt = _set_copy_reshelving($copy, $requestor->id, $ctx->{session}); 
1763         return $evt if $evt;
1764
1765 #       $copy->status($U->copy_status_from_name('reshelving')->id);
1766 #       $evt = $U->update_copy( session => $session, 
1767 #               copy => $copy, editor => $requestor->id );
1768 #       return $evt if $evt;
1769
1770         $ctx->{session}->request(
1771                 'open-ils.storage.direct.action.circulation.update', $circ )->gather(1);
1772
1773         return undef;
1774 }
1775
1776 sub _set_copy_reshelving {
1777         my( $copy, $reqr, $session ) = @_;
1778
1779         $logger->info("Setting copy ".$copy->id." to reshelving");
1780         $copy->status($U->copy_status_from_name('reshelving')->id);
1781
1782         my $evt = $U->update_copy( 
1783                 session => $session, 
1784                 copy            => $copy, 
1785                 editor  => $reqr
1786                 );
1787         return $evt if $evt;
1788 }
1789
1790 # returns event on error, undef on success
1791 # This voids all bills attached to the given circulation that occurred
1792 # after the backdate 
1793 # THIS DOES NOT CLOSE THE CIRC if there are no more fines on the item
1794 sub _checkin_handle_backdate {
1795         my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1796
1797         $logger->activity("User ".$requestor->id.
1798                 " backdating circ [".$circ->target_copy."] to date: $backdate");
1799
1800         my $bills = $session->request( # XXX Verify this call is correct
1801                 "open-ils.storage.direct.money.billing.search_where.atomic",
1802                 billing_ts => { ">=" => $backdate }, "xact" => $circ->id )->gather(1);
1803
1804         if($bills) {
1805                 for my $bill (@$bills) {
1806                         $bill->voided('t');
1807                         my $s = $session->request(
1808                                 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1809                         return $U->DB_UPDATE_FAILED($bill) unless $s;
1810                 }
1811         }
1812
1813         # if the caller elects to attempt to close the circulation
1814         # transaction, then it will be closed if there are not further
1815         # charges on the transaction
1816         #if( $closecirc ) {
1817                 #my ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1818            #return $evt if $evt;
1819                 #$circ->xact_finish($backdate) if $obt->balance_owed <= 0;
1820         #}
1821
1822         return undef;
1823 }
1824
1825
1826 sub _find_patron_from_params {
1827         my $params = shift;
1828
1829         my $patron;
1830         my $copy;
1831         my $circ;
1832         my $evt;
1833
1834         if(my $barcode = $params->{barcode}) {
1835                 $logger->debug("circ finding user from params with barcode $barcode");
1836                 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
1837                 return (undef, undef, $evt) if $evt;
1838                 ($circ, $evt) = $U->fetch_open_circulation($copy->id);
1839                 return (undef, undef, $evt) if $evt;
1840                 ($patron, $evt) = $U->fetch_user($circ->usr);
1841                 return (undef, undef, $evt) if $evt;
1842         }
1843         return ($patron, $copy);
1844 }
1845
1846
1847 # ------------------------------------------------------------------------------
1848
1849 __PACKAGE__->register_method(
1850         method  => "renew",
1851         api_name        => "open-ils.circ.renew.override",
1852         signature       => q/@see open-ils.circ.renew/,
1853 );
1854
1855
1856 __PACKAGE__->register_method(
1857         method  => "renew",
1858         api_name        => "open-ils.circ.renew",
1859         notes           => <<"  NOTES");
1860         PARAMS( authtoken, circ => circ_id );
1861         open-ils.circ.renew(login_session, circ_object);
1862         Renews the provided circulation.  login_session is the requestor of the
1863         renewal and if the logged in user is not the same as circ->usr, then
1864         the logged in user must have RENEW_CIRC permissions.
1865         NOTES
1866
1867 sub renew {
1868         my( $self, $client, $authtoken, $params ) = @_;
1869         $U->logmark;
1870
1871         my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
1872         $__isrenewal = 1;
1873
1874         $params->{override} = 1 if $self->api_name =~ /override/o;
1875
1876         # fetch the patron object one way or another
1877         if( $params->{patron} ) {
1878                 ( $patron, $evt ) = $U->fetch_user($params->{patron});
1879                 if($evt) { $__isrenewal = 0; return $evt; }
1880
1881         } elsif( $params->{patron_barcode} ) {
1882                 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
1883                 if($evt) { $__isrenewal = 0; return $evt; }
1884
1885         } else {
1886                 ($patron, $copy, $evt) = _find_patron_from_params($params);
1887                 return $evt if $evt;
1888                 $params->{copy} = $copy;
1889         }
1890
1891         # verify our login session
1892         ($requestor, $evt) = $U->checkses($authtoken);
1893         if($evt) { $__isrenewal = 0; return $evt; }
1894
1895         # make sure we have permission to perform a renewal
1896         if( $requestor->id ne $patron->id ) {
1897                 $evt = $U->check_perms($requestor->id, $requestor->ws_ou, 'RENEW_CIRC');
1898                 if($evt) { $__isrenewal = 0; return $evt; }
1899         }
1900
1901
1902         # fetch and build the circulation environment
1903         ( $ctx, $evt ) = create_circ_ctx( %$params, 
1904                 patron                                                  => $patron, 
1905                 requestor                                               => $requestor, 
1906                 patron                                                  => $patron, 
1907                 type                                                            => 'circ',
1908                 #fetch_patron_circ_summary      => 1,
1909                 fetch_copy_statuses                     => 1, 
1910                 fetch_copy_locations                    => 1, 
1911                 );
1912         if($evt) { $__isrenewal = 0; return $evt; }
1913         $params->{_ctx} = $ctx;
1914
1915         # make sure they have some renewals left and make sure the circulation exists
1916         ($circ, $evt) = _check_renewal_remaining($ctx);
1917         if($evt) { $__isrenewal = 0; return $evt; }
1918         $ctx->{old_circ} = $circ;
1919         my $renewals = $circ->renewal_remaining - 1;
1920
1921         # run the renew permit script
1922         $evt = _run_renew_scripts($ctx);
1923         if($evt) { $__isrenewal = 0; return $evt; }
1924
1925         # checkin the cop
1926         #$ctx->{patron} = $ctx->{patron}->id;
1927         $evt = $self->generic_receive($client, $authtoken, $ctx );
1928                 #{ barcode => $params->{barcode}, patron => $params->{patron}} );
1929
1930         if( !$U->event_equals($evt, 'SUCCESS') ) {
1931                 $__isrenewal = 0; return $evt; 
1932         }
1933
1934         # re-fetch the context since objects have changed in the checkin
1935         ( $ctx, $evt ) = create_circ_ctx( %$params, 
1936                 patron                                                  => $patron, 
1937                 requestor                                               => $requestor, 
1938                 patron                                                  => $patron, 
1939                 type                                                            => 'circ',
1940                 #fetch_patron_circ_summary      => 1,
1941                 fetch_copy_statuses                     => 1, 
1942                 fetch_copy_locations                    => 1, 
1943                 );
1944         if($evt) { $__isrenewal = 0; return $evt; }
1945         $params->{_ctx} = $ctx;
1946         $ctx->{renewal_remaining} = $renewals;
1947
1948         # run the circ permit scripts
1949         if( $ctx->{permit_override} ) {
1950                 $evt = $U->check_perms(
1951                         $requestor->id, $ctx->{copy}->circ_lib->id, 'CIRC_PERMIT_OVERRIDE');
1952                 if($evt) { $__isrenewal = 0; return $evt; }
1953
1954         } else {
1955                 $evt = $self->permit_circ( $client, $authtoken, $params );
1956                 if( $U->event_equals($evt, 'ITEM_NOT_CATALOGED')) {
1957                         #$ctx->{precat} = 1;
1958                         $params->{precat} = 1;
1959
1960                 } else {
1961                         if(!$U->event_equals($evt, 'SUCCESS')) {
1962                                 if($evt) { $__isrenewal = 0; return $evt; }
1963                         }
1964                 }
1965                 $params->{permit_key} = $evt->{payload};
1966         }
1967
1968
1969         # checkout the item again
1970         $params->{patron} = $ctx->{patron}->id;
1971         $evt = $self->checkout($client, $authtoken, $params );
1972
1973         $logger->activity("user ".$requestor->id." renewl of item ".
1974                 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1975
1976         $__isrenewal = 0;
1977         return $evt;
1978 }
1979
1980 sub _check_renewal_remaining {
1981         my $ctx = shift;
1982         $U->logmark;
1983         my( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
1984         return (undef, $evt) if $evt;
1985         $evt = OpenILS::Event->new(
1986                 'MAX_RENEWALS_REACHED') if $circ->renewal_remaining < 1;
1987         return ($circ, $evt);
1988 }
1989
1990 sub _run_renew_scripts {
1991         my $ctx = shift;
1992         my $runner = $ctx->{runner};
1993         $U->logmark;
1994
1995         $runner->load($scripts{circ_permit_renew});
1996         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
1997
1998         my $events = $runner->retrieve('result.events');
1999         $events = [ split(/,/, $events) ]; 
2000         $logger->activity("circ_permit_renew for user ".
2001                 $ctx->{patron}->id." returned events: @$events") if @$events;
2002
2003         my @allevents;
2004         push( @allevents, OpenILS::Event->new($_)) for @$events;
2005         return \@allevents if  @allevents;
2006
2007         return undef;
2008 }
2009
2010         
2011
2012
2013 1;
2014