]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
added circ fetcher which includes stop_fines items
[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         }
335
336         $ctx->{runner} = $runner;
337         return $runner;
338 }
339
340
341 sub _add_script_runner_methods {
342         my $ctx = shift;
343         $U->logmark;
344         my $runner = $ctx->{runner};
345
346         if( $ctx->{copy} ) {
347                 
348                 # allows a script to fetch a hold that is currently targeting the
349                 # copy in question
350                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
351                                 my $key = shift;
352                                 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
353                                 $hold = undef unless $hold;
354                                 $runner->insert( $key, $hold, 1 );
355                         }
356                 );
357         }
358 }
359
360 # ------------------------------------------------------------------------------
361
362 __PACKAGE__->register_method(
363         method  => "permit_circ",
364         api_name        => "open-ils.circ.checkout.permit",
365         notes           => q/
366                 Determines if the given checkout can occur
367                 @param authtoken The login session key
368                 @param params A trailing hash of named params including 
369                         barcode : The copy barcode, 
370                         patron : The patron the checkout is occurring for, 
371                         renew : true or false - whether or not this is a renewal
372                 @return The event that occurred during the permit check.  
373         /);
374
375 __PACKAGE__->register_method (
376         method          => 'permit_circ',
377         api_name                => 'open-ils.circ.checkout.permit.override',
378         signature       => q/@see open-ils.circ.checkout.permit/,
379 );
380
381 sub permit_circ {
382         my( $self, $client, $authtoken, $params ) = @_;
383         $U->logmark;
384
385         my $override = $params->{override} = 1 if $self->api_name =~ /override/o;
386
387         my ( $requestor, $patron, $ctx, $evt, $circ );
388
389         # check permisson of the requestor
390         ( $requestor, $patron, $evt ) = 
391                 $U->checkses_requestor( 
392                 $authtoken, $params->{patron}, 'VIEW_PERMIT_CHECKOUT' );
393         return $evt if $evt;
394
395
396         # fetch and build the circulation environment
397         if( !( $ctx = $params->{_ctx}) ) {
398
399                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
400                         patron                                                  => $patron, 
401                         requestor                                               => $requestor, 
402                         type                                                            => 'circ',
403                         #fetch_patron_circ_summary      => 1,
404                         fetch_copy_statuses                     => 1, 
405                         fetch_copy_locations                    => 1, 
406                         );
407                 return $evt if $evt;
408         }
409
410         my $copy = $ctx->{copy};
411         if($copy) {
412                 my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
413                 return OpenILS::Event->new('COPY_IN_TRANSIT') 
414                         if $stat == $U->copy_status_from_name('in transit')->id;
415         }
416
417         $ctx->{authtoken} = $authtoken;
418
419         $evt = undef;
420         if( $ctx->{copy} and ($evt = _handle_claims_returned($ctx)) ) {
421                 return $evt unless $U->event_equals($evt, 'SUCCESS');
422         }
423
424         if($evt) { 
425                 $evt = undef;
426
427         } else { 
428
429                 # no claims returned circ was found, check if there is any open circ
430                 if( !$ctx->{ishold} and !$__isrenewal and $ctx->{copy} ) {
431                         ($circ, $evt) = $U->fetch_open_circulation($ctx->{copy}->id);
432                         return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
433                 }
434         }
435
436
437         $ctx->{permit_key} = _cache_permit_key();
438         my $events = _run_permit_scripts($ctx);
439
440         if( $override ) {
441                 $evt = override_events($requestor, $requestor->ws_ou, 
442                         $events, $authtoken, $ctx->{copy}->id, $client);
443                 return $evt if $evt;
444                 return OpenILS::Event->new('SUCCESS', payload => $ctx->{permit_key} );
445         }
446
447         return $events;
448 }
449
450 sub override_events {
451
452         my( $requestor, $org, $events, $authtoken, $copyid, $conn ) = @_;
453         $events = [ $events ] unless ref($events) eq 'ARRAY';
454         my @failed;
455
456         for my $e (@$events) {
457                 my $tc = $e->{textcode};
458                 next if $tc eq 'SUCCESS';
459                 my $ov = "$tc.override";
460                 $logger->info("attempting to override event $ov");
461                 my $evt = $U->check_perms( $requestor->id, $org, $ov );
462                 return $evt if $evt;
463         }
464
465         return undef;
466 }
467
468
469 __PACKAGE__->register_method(
470         method  => "check_title_hold",
471         api_name        => "open-ils.circ.title_hold.is_possible",
472         notes           => q/
473                 Determines if a hold were to be placed by a given user,
474                 whether or not said hold would have any potential copies
475                 to fulfill it.
476                 @param authtoken The login session key
477                 @param params A hash of named params including:
478                         patronid  - the id of the hold recipient
479                         titleid (brn) - the id of the title to be held
480                         depth   - the hold range depth (defaults to 0)
481         /);
482
483 # XXX add pickup lib to the call to test for perms
484
485 sub check_title_hold {
486         my( $self, $client, $authtoken, $params ) = @_;
487         my %params = %$params;
488         my $titleid = $params{titleid};
489
490         my ( $requestor, $patron, $evt ) = $U->checkses_requestor( 
491                 $authtoken, $params{patronid}, 'VIEW_HOLD_PERMIT' );
492         return $evt if $evt;
493
494         my $rangelib    = $patron->home_ou;
495         my $depth               = $params{depth} || 0;
496         my $pickup              = $params{pickup_lib};
497
498         $logger->debug("Fetching ranged title tree for title $titleid, org $rangelib, depth $depth");
499
500         my $org = $U->simplereq(
501                 'open-ils.actor', 
502                 'open-ils.actor.org_unit.retrieve', 
503                 $authtoken, $requestor->home_ou );
504
505         my $limit       = 10;
506         my $offset      = 0;
507         my $title;
508
509         while( $title = $U->storagereq(
510                                 'open-ils.storage.biblio.record_entry.ranged_tree', 
511                                 $titleid, $rangelib, $depth, $limit, $offset ) ) {
512
513                 last unless 
514                         ref($title) and 
515                         ref($title->call_numbers) and 
516                         @{$title->call_numbers};
517
518                 for my $cn (@{$title->call_numbers}) {
519         
520                         $logger->debug("Checking callnumber ".$cn->id." for hold fulfillment possibility");
521         
522                         for my $copy (@{$cn->copies}) {
523         
524                                 $logger->debug("Checking copy ".$copy->id." for hold fulfillment possibility");
525         
526                                 return 1 if OpenILS::Utils::PermitHold::permit_copy_hold(
527                                         {       patron                          => $patron, 
528                                                 requestor                       => $requestor, 
529                                                 copy                                    => $copy,
530                                                 title                                   => $title, 
531                                                 title_descriptor        => $title->fixed_fields, # this is fleshed into the title object
532                                                 pickup_lib                      => $pickup,
533                                                 request_lib                     => $org } );
534         
535                                 $logger->debug("Copy ".$copy->id." for hold fulfillment possibility failed...");
536                         }
537                 }
538
539                 $offset += $limit;
540         }
541
542         return 0;
543 }
544
545
546 # Runs the patron and copy permit scripts
547 # if this is a non-cat circulation, the copy permit script 
548 # is not run
549 sub _run_permit_scripts {
550
551         my $ctx                 = shift;
552         my $runner              = $ctx->{runner};
553         my $patronid    = $ctx->{patron}->id;
554         my $barcode             = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
555         my $key                 = $ctx->{permit_key};
556
557
558
559         # ---------------------------------------------------------------------
560         # Find all of the fatal penalties currently set on the user
561         # ---------------------------------------------------------------------
562         my $penalties = $U->update_patron_penalties( 
563                 authtoken => $ctx->{authtoken}, 
564                 patron    => $ctx->{patron} 
565         );
566
567         $penalties = $penalties->{fatal_penalties};
568         $logger->info("circ patron penalties user $patronid: @$penalties");
569
570
571         # ---------------------------------------------------------------------
572         # Now run the patron permit script 
573         # ---------------------------------------------------------------------
574         $runner->load($scripts{circ_permit_patron});
575         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
576
577         my $patron_events = $runner->retrieve('result.events');
578         $patron_events = [ split(/,/, $patron_events) ]; 
579         $ctx->{circ_permit_patron_events} = $patron_events;
580         $logger->activity("circ_permit_patron for returned @$patron_events") if @$patron_events;
581
582         my @evts_so_far = (@$penalties, @$patron_events);
583
584         return \@evts_so_far if @evts_so_far;
585
586
587         if( $ctx->{noncat} ) {
588                 $logger->debug("Exiting circ permit early because item is a non-cataloged item");
589                 return OpenILS::Event->new('SUCCESS', payload => $key);
590         }
591
592         if($ctx->{precat}) {
593                 $logger->debug("Exiting circ permit early because copy is pre-cataloged");
594                 return OpenILS::Event->new('ITEM_NOT_CATALOGED', payload => $key);
595         }
596
597         if($ctx->{ishold}) {
598                 $logger->debug("Exiting circ permit early because request is for hold patron permit");
599                 return OpenILS::Event->new('SUCCESS');
600         }
601
602
603
604         # ---------------------------------------------------------------------
605         # Capture all of the copy permit events
606         # ---------------------------------------------------------------------
607         $runner->load($scripts{circ_permit_copy});
608         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
609
610         my $copy_events = $runner->retrieve('result.events');
611         $copy_events = [ split(/,/, $copy_events) ]; 
612         $ctx->{circ_permit_copy_events} = $copy_events;
613         $logger->activity("circ_permit_copy for copy ".
614                 "$barcode returned events: @$copy_events") if @$copy_events;
615
616
617
618
619         # ---------------------------------------------------------------------
620         # Now collect all of the events together
621         # ---------------------------------------------------------------------
622         my @allevents = ( @evts_so_far, @$copy_events );
623
624         #push( @allevents, OpenILS::Event->new($_)) for @$penalties;
625         #push( @allevents, OpenILS::Event->new($_)) for @$copy_events;
626
627         my $ae = _check_copy_alert($ctx->{copy});
628         push( @allevents, $ae ) if $ae;
629
630         return OpenILS::Event->new('SUCCESS', payload => $key) unless (@allevents);
631
632         # uniquify 
633         my %hash = map { ($_->{ilsevent} => $_) } @allevents;
634         @allevents = values %hash;
635
636         for (@allevents) {
637                 $_->{payload} = $ctx->{copy}->status->id
638                         if ($_->{textcode} eq 'COPY_NOT_AVAILABLE');
639         }
640
641         return \@allevents;
642 }
643
644 sub _check_copy_alert {
645         my $copy = shift;
646         return OpenILS::Event->new('COPY_ALERT_MESSAGE', 
647                 payload => $copy->alert_message) if $copy->alert_message;
648         return undef;
649 }
650
651 # takes copyid, patronid, and requestor id
652 sub _cache_permit_key {
653         my $key = md5_hex( time() . rand() . "$$" );
654         $logger->debug("Setting circ permit key to $key");
655         $cache_handle->put_cache( "oils_permit_key_$key", 1, 300 );
656         return $key;
657 }
658
659 sub _check_permit_key {
660         my $key = shift;
661         $logger->debug("Fetching circ permit key $key");
662         my $k = "oils_permit_key_$key";
663         my $one = $cache_handle->get_cache($k);
664         $cache_handle->delete_cache($k);
665         return ($one) ? 1 : 0;
666 }
667
668
669 # ------------------------------------------------------------------------------
670
671 __PACKAGE__->register_method(
672         method  => "checkout",
673         api_name        => "open-ils.circ.checkout",
674         notes => q/
675                 Checks out an item
676                 @param authtoken The login session key
677                 @param params A named hash of params including:
678                         copy                    The copy object
679                         barcode         If no copy is provided, the copy is retrieved via barcode
680                         copyid          If no copy or barcode is provide, the copy id will be use
681                         patron          The patron's id
682                         noncat          True if this is a circulation for a non-cataloted item
683                         noncat_type     The non-cataloged type id
684                         noncat_circ_lib The location for the noncat circ.  
685                         precat          The item has yet to be cataloged
686                         dummy_title The temporary title of the pre-cataloded item
687                         dummy_author The temporary authr of the pre-cataloded item
688                                 Default is the home org of the staff member
689                 @return The SUCCESS event on success, any other event depending on the error
690         /);
691
692 sub checkout {
693         my( $self, $client, $authtoken, $params ) = @_;
694         $U->logmark;
695
696         my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
697         my $key = $params->{permit_key};
698
699         # if this is a renewal, then the requestor does not have to
700         # have checkout privelages
701         ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
702         ( $requestor, $evt ) = $U->checksesperm( $authtoken, 'COPY_CHECKOUT' ) unless $__isrenewal;
703         return $evt if $evt;
704
705         if( $params->{patron} ) {
706                 ( $patron, $evt ) = $U->fetch_user($params->{patron});
707                 return $evt if $evt;
708         } else {
709                 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
710                 return $evt if $evt;
711         }
712
713         # set the circ lib to the home org of the requestor if not specified
714         my $circlib = (defined($params->{circ_lib})) ? 
715                 $params->{circ_lib} : $requestor->ws_ou;
716
717
718         # Make sure the caller has a valid permit key or is 
719         # overriding the permit can
720         if( $params->{permit_override} ) {
721                 $evt = $U->check_perms(
722                         $requestor->id, $requestor->ws_ou, 'CIRC_PERMIT_OVERRIDE');
723                 return $evt if $evt;
724
725         } else {
726                 return OpenILS::Event->new('CIRC_PERMIT_BAD_KEY') 
727                         unless _check_permit_key($key);
728         }
729
730         # if this is a non-cataloged item, check it out and return
731         return _checkout_noncat( 
732                 $key, $requestor, $patron, %$params ) if $params->{noncat};
733
734         # if this item has yet to be cataloged, make sure a dummy copy exists
735         ( $params->{copy}, $evt ) = _make_precat_copy(
736                 $requestor, $circlib, $params ) if $params->{precat};
737         return $evt if $evt;
738
739
740         # fetch and build the circulation environment
741         if( !( $ctx = $params->{_ctx}) ) {
742                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
743                         patron                                                  => $patron, 
744                         requestor                                               => $requestor, 
745                         session                                                 => $U->start_db_session(),
746                         type                                                            => 'circ',
747                         #fetch_patron_circ_summary      => 1,
748                         fetch_copy_statuses                     => 1, 
749                         fetch_copy_locations                    => 1, 
750                         );
751                 return $evt if $evt;
752         }
753         $ctx->{session} = $U->start_db_session() unless $ctx->{session};
754
755         # if the call doesn't know it's not cataloged..
756         if(!$params->{precat}) {
757                 if( $ctx->{copy}->call_number eq '-1' ) {
758                         return OpenILS::Event->new('ITEM_NOT_CATALOGED');
759                 }
760         }
761
762
763         $copy = $ctx->{copy};
764         if($copy) {
765                 my $stat = (ref $copy->status) ? $copy->status->id : $copy->status;
766                 return OpenILS::Event->new('COPY_IN_TRANSIT') 
767                         if $stat == $U->copy_status_from_name('in transit')->id;
768         }
769
770         # this happens in permit.. but we need to check here for 'offline' requests
771         ($circ) = $U->fetch_open_circulation($ctx->{copy}->id);
772         return OpenILS::Event->new('OPEN_CIRCULATION_EXISTS') if $circ;
773
774         my $cid = ($params->{precat}) ? -1 : $ctx->{copy}->id;
775
776
777         $ctx->{circ_lib} = $circlib;
778
779         $evt = _run_checkout_scripts($ctx);
780         return $evt if $evt;
781
782
783         _build_checkout_circ_object($ctx);
784
785         $evt = _apply_modified_due_date($ctx);
786         return $evt if $evt;
787
788         $evt = _commit_checkout_circ_object($ctx);
789         return $evt if $evt;
790
791         $evt = _update_checkout_copy($ctx);
792         return $evt if $evt;
793
794         my $holds;
795         ($holds, $evt) = _handle_related_holds($ctx);
796         return $evt if $evt;
797
798
799         $logger->debug("Checkout committing objects with session thread trace: ".$ctx->{session}->session_id);
800         $U->commit_db_session($ctx->{session});
801         my $record = $U->record_to_mvr($ctx->{title}) unless $ctx->{precat};
802
803         $logger->activity("user ".$requestor->id." successfully checked out item ".
804                 $ctx->{copy}->barcode." to user ".$ctx->{patron}->id );
805
806
807         # ------------------------------------------------------------------------------
808         # Update the patron penalty info in the DB
809         # ------------------------------------------------------------------------------
810         $U->update_patron_penalties( 
811                 authtoken => $authtoken, 
812                 patron    => $ctx->{patron} ,
813                 background      => 1,
814         );
815
816         return OpenILS::Event->new('SUCCESS', 
817                 payload => { 
818                         copy                                    => $U->unflesh_copy($ctx->{copy}),
819                         circ                                    => $ctx->{circ},
820                         record                          => $record,
821                         holds_fulfilled => $holds,
822                 } 
823         )
824 }
825
826
827 sub _make_precat_copy {
828         my ( $requestor, $circlib, $params ) =  @_;
829         $U->logmark;
830         my( $copy, undef ) = _find_copy_by_attr(%$params);
831
832         if($copy) {
833                 $logger->debug("Pre-cat copy already exists in checkout: ID=" . $copy->id);
834
835                 $copy->editor($requestor->id);
836                 $copy->edit_date('now');
837                 $copy->dummy_title($params->{dummy_title});
838                 $copy->dummy_author($params->{dummy_author});
839
840                 my $stat = $U->storagereq(
841                         'open-ils.storage.direct.asset.copy.update', $copy );
842
843                 return (undef, $U->DB_UPDATE_FAILED($copy)) unless $stat;
844                 return ($copy);
845         }
846
847         $logger->debug("Creating a new precataloged copy in checkout with barcode " . $params->{barcode});
848
849         my $evt = OpenILS::Event->new(
850                 'BAD_PARAMS', desc => "Dummy title or author not provided" ) 
851                 unless ( $params->{dummy_title} and $params->{dummy_author} );
852         return (undef, $evt) if $evt;
853
854         $copy = Fieldmapper::asset::copy->new;
855         $copy->circ_lib($circlib);
856         $copy->creator($requestor->id);
857         $copy->editor($requestor->id);
858         $copy->barcode($params->{barcode});
859         $copy->call_number(-1); #special CN for precat materials
860         $copy->loan_duration(&PRECAT_LOAN_DURATION); 
861         $copy->fine_level(&PRECAT_FINE_LEVEL);
862
863         $copy->dummy_title($params->{dummy_title});
864         $copy->dummy_author($params->{dummy_author});
865
866         my $id = $U->storagereq(
867                 'open-ils.storage.direct.asset.copy.create', $copy );
868         return (undef, $U->DB_UPDATE_FAILED($copy)) unless $copy;
869
870         $logger->debug("Pre-cataloged copy successfully created");
871         return ($U->fetch_copy($id));
872 }
873
874
875 sub _run_checkout_scripts {
876         my $ctx = shift;
877         $U->logmark;
878         my $evt;
879         my $circ;
880
881         my $runner = $ctx->{runner};
882
883         $runner->insert('result.durationLevel');
884         $runner->insert('result.durationRule');
885         $runner->insert('result.recurringFinesRule');
886         $runner->insert('result.recurringFinesLevel');
887         $runner->insert('result.maxFine');
888
889         $runner->load($scripts{circ_duration});
890         $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
891         my $duration = $runner->retrieve('result.durationRule');
892         $logger->debug("Circ duration script yielded a duration rule of: $duration");
893
894         $runner->load($scripts{circ_recurring_fines});
895         $runner->run or throw OpenSRF::EX::ERROR ("Circ Recurring Fines Script Died: $@");
896         my $recurring = $runner->retrieve('result.recurringFinesRule');
897         $logger->debug("Circ recurring fines script yielded a rule of: $recurring");
898
899         $runner->load($scripts{circ_max_fines});
900         $runner->run or throw OpenSRF::EX::ERROR ("Circ Max Fine Script Died: $@");
901         my $max_fine = $runner->retrieve('result.maxFine');
902         $logger->debug("Circ max_fine fines script yielded a rule of: $max_fine");
903
904         ($duration, $evt) = $U->fetch_circ_duration_by_name($duration);
905         return $evt if $evt;
906         ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring);
907         return $evt if $evt;
908         ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine);
909         return $evt if $evt;
910
911         $ctx->{duration_level}                  = $runner->retrieve('result.durationLevel');
912         $ctx->{recurring_fines_level} = $runner->retrieve('result.recurringFinesLevel');
913         $ctx->{duration_rule}                   = $duration;
914         $ctx->{recurring_fines_rule}    = $recurring;
915         $ctx->{max_fine_rule}                   = $max_fine;
916
917         return undef;
918 }
919
920 sub _build_checkout_circ_object {
921         my $ctx = shift;
922         $U->logmark;
923
924         my $circ                        = new Fieldmapper::action::circulation;
925         my $duration    = $ctx->{duration_rule};
926         my $max                 = $ctx->{max_fine_rule};
927         my $recurring   = $ctx->{recurring_fines_rule};
928         my $copy                        = $ctx->{copy};
929         my $patron              = $ctx->{patron};
930         my $dur_level   = $ctx->{duration_level};
931         my $rec_level   = $ctx->{recurring_fines_level};
932
933         $circ->duration( $duration->shrt ) if ($dur_level == 1);
934         $circ->duration( $duration->normal ) if ($dur_level == 2);
935         $circ->duration( $duration->extended ) if ($dur_level == 3);
936
937         $circ->recuring_fine( $recurring->low ) if ($rec_level =~ /low/io);
938         $circ->recuring_fine( $recurring->normal ) if ($rec_level =~ /normal/io);
939         $circ->recuring_fine( $recurring->high ) if ($rec_level =~ /high/io);
940
941         $circ->duration_rule( $duration->name );
942         $circ->recuring_fine_rule( $recurring->name );
943         $circ->max_fine_rule( $max->name );
944         $circ->max_fine( $max->amount );
945
946         $circ->fine_interval($recurring->recurance_interval);
947         $circ->renewal_remaining( $duration->max_renewals );
948         $circ->target_copy( $copy->id );
949         $circ->usr( $patron->id );
950         $circ->circ_lib( $ctx->{circ_lib} );
951
952         if( $__isrenewal ) {
953                 $logger->debug("Circ is a renewal.  Setting renewal_remaining to " . $ctx->{renewal_remaining} );
954                 $circ->opac_renewal(1); 
955                 $circ->renewal_remaining($ctx->{renewal_remaining});
956                 $circ->circ_staff($ctx->{requestor}->id);
957         } 
958
959
960         # if the user provided an overiding checkout time, 
961         # (e.g. the checkout really happened several hours ago), then
962         # we apply that here.  Does this need a perm??
963         if( my $ds = _create_date_stamp($ctx->{checkout_time}) ) {
964                 $logger->debug("circ setting checkout_time to $ds");
965                 $circ->xact_start($ds);
966         }
967
968         # if a patron is renewing, 'requestor' will be the patron
969         $circ->circ_staff($ctx->{requestor}->id ); 
970         _set_circ_due_date($circ);
971         $ctx->{circ} = $circ;
972 }
973
974 sub _apply_modified_due_date {
975         my $ctx = shift;
976         my $circ = $ctx->{circ};
977
978         $U->logmark;
979
980         if( $ctx->{due_date} ) {
981
982                 my $evt = $U->check_perms(
983                         $ctx->{requestor}->id, $ctx->{circ_lib}, 'CIRC_OVERRIDE_DUE_DATE');
984                 return $evt if $evt;
985
986                 my $ds = _create_date_stamp($ctx->{due_date});
987                 $logger->debug("circ modifying  due_date to $ds");
988                 $circ->due_date($ds);
989
990         } else {
991
992                 # if the due_date lands on a day when the location is closed
993                 my $copy = $ctx->{copy};
994                 return unless $copy;
995
996                 $logger->info("circ searching for closed date overlap on lib ".
997                         $copy->circ_lib->id ." with an item due date of ".$circ->due_date );
998
999                 my $dateinfo = $ctx->{session}->request(
1000                         'open-ils.storage.actor.org_unit.closed_date.overlap',
1001                         $copy->circ_lib->id, $circ->due_date )->gather(1);
1002
1003
1004                 if($dateinfo) {
1005                         $logger->info("$dateinfo : circ due data / close date overlap found : due_date=".
1006                                 $circ->due_date." start=". $dateinfo->{start}.", end=".$dateinfo->{end});
1007
1008                                 # XXX make the behavior more dynamic
1009                                 # for now, we just push the due date to after the close date
1010                                 $circ->due_date($dateinfo->{end});
1011                 }
1012
1013         }
1014         return undef;
1015 }
1016
1017 sub _create_date_stamp {
1018         my $datestring = shift;
1019         return undef unless $datestring;
1020         $datestring = clense_ISO8601($datestring);
1021         $logger->debug("circ created date stamp => $datestring");
1022         return $datestring;
1023 }
1024
1025 sub _create_due_date {
1026         my $duration = shift;
1027         $U->logmark;
1028         my ($sec,$min,$hour,$mday,$mon,$year) = 
1029                 gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time()));
1030         $year += 1900; $mon += 1;
1031         my $due_date = sprintf(
1032         '%s-%0.2d-%0.2dT%s:%0.2d:%0.2d-00',
1033         $year, $mon, $mday, $hour, $min, $sec);
1034         return $due_date;
1035 }
1036
1037 sub _set_circ_due_date {
1038         my $circ = shift;
1039         $U->logmark;
1040         my $dd = _create_due_date($circ->duration);
1041         $logger->debug("Checkout setting due date on circ to: $dd");
1042         $circ->due_date($dd);
1043 }
1044
1045 # Sets the editor, edit_date, un-fleshes the copy, and updates the copy in the DB
1046 sub _update_checkout_copy {
1047         my $ctx = shift;
1048         $U->logmark;
1049         my $copy = $ctx->{copy};
1050
1051         my $s = $U->copy_status_from_name('checked out');
1052         $copy->status( $s->id ) if $s;
1053
1054         my $evt = $U->update_copy( session => $ctx->{session}, 
1055                 copy => $copy, editor => $ctx->{requestor}->id );
1056         return (undef,$evt) if $evt;
1057
1058         return undef;
1059 }
1060
1061 # commits the circ object to the db then fleshes the circ with rules objects
1062 sub _commit_checkout_circ_object {
1063
1064         my $ctx = shift;
1065         my $circ = $ctx->{circ};
1066         $U->logmark;
1067
1068         $circ->clear_id;
1069         my $r = $ctx->{session}->request(
1070                 "open-ils.storage.direct.action.circulation.create", $circ )->gather(1);
1071
1072         return $U->DB_UPDATE_FAILED($circ) unless $r;
1073
1074         $logger->debug("Created a new circ object in checkout: $r");
1075
1076         $circ->id($r);
1077         $circ->duration_rule($ctx->{duration_rule});
1078         $circ->max_fine_rule($ctx->{max_fine_rule});
1079         $circ->recuring_fine_rule($ctx->{recurring_fines_rule});
1080
1081         return undef;
1082 }
1083
1084
1085 # sees if there are any holds that this copy 
1086 sub _handle_related_holds {
1087
1088         my $ctx         = shift;
1089         my $copy                = $ctx->{copy};
1090         my $patron      = $ctx->{patron};
1091         my $holds       = $holdcode->fetch_related_holds($copy->id);
1092         $U->logmark;
1093         my @fulfilled;
1094
1095         # XXX We should only fulfill one hold here...
1096         # XXX If a hold was transited to the user who is checking out
1097         # the item, we need to make sure that hold is what's grabbed
1098         if(ref($holds) && @$holds) {
1099
1100                 # for now, just sort by id to get what should be the oldest hold
1101                 $holds = [ sort { $a->id <=> $b->id } @$holds ];
1102                 my @myholds = grep { $_->usr eq $patron->id } @$holds;
1103                 my @altholds    = grep { $_->usr ne $patron->id } @$holds;
1104
1105                 if(@myholds) {
1106                         my $hold = $myholds[0];
1107
1108                         $logger->debug("Related hold found in checkout: " . $hold->id );
1109
1110                         $hold->current_copy($copy->id); # just make sure it's set
1111                         # if the hold was never officially captured, capture it.
1112                         $hold->capture_time('now') unless $hold->capture_time;
1113                         $hold->fulfillment_time('now');
1114                         my $r = $ctx->{session}->request(
1115                                 "open-ils.storage.direct.action.hold_request.update", $hold )->gather(1);
1116                         return (undef,$U->DB_UPDATE_FAILED( $hold )) unless $r;
1117                         push( @fulfilled, $hold->id );
1118                 }
1119
1120                 # If there are any holds placed for other users that point to this copy,
1121                 # then we need to un-target those holds so the targeter can pick a new copy
1122                 for(@altholds) {
1123
1124                         $logger->info("Un-targeting hold ".$_->id.
1125                                 " because copy ".$copy->id." is getting checked out");
1126
1127                         $_->clear_current_copy;
1128                         my $r = $ctx->{session}->request(
1129                                 "open-ils.storage.direct.action.hold_request.update", $_ )->gather(1);
1130                         return (undef,$U->DB_UPDATE_FAILED( $_ )) unless $r;
1131                 }
1132         }
1133
1134         return (\@fulfilled, undef);
1135 }
1136
1137 sub _checkout_noncat {
1138         my ( $key, $requestor, $patron, %params ) = @_;
1139         my( $circ, $circlib, $evt );
1140         $U->logmark;
1141
1142         $circlib = $params{noncat_circ_lib} || $requestor->ws_ou;
1143
1144         my $count = $params{noncat_count} || 1;
1145         my $cotime = _create_date_stamp($params{checkout_time}) || "";
1146         $logger->info("circ creating $count noncat circs with checkout time $cotime");
1147         for(1..$count) {
1148                 ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ(
1149                         $requestor->id, $patron->id, $circlib, $params{noncat_type}, $cotime );
1150                 return $evt if $evt;
1151         }
1152
1153         return OpenILS::Event->new( 
1154                 'SUCCESS', payload => { noncat_circ => $circ } );
1155 }
1156
1157
1158 __PACKAGE__->register_method(
1159         method  => "generic_receive",
1160         api_name        => "open-ils.circ.checkin",
1161         argc            => 2,
1162         signature       => q/
1163                 Generic super-method for handling all copies
1164                 @param authtoken The login session key
1165                 @param params Hash of named parameters including:
1166                         barcode - The copy barcode
1167                         force           - If true, copies in bad statuses will be checked in and give good statuses
1168                         ...
1169         /
1170 );
1171
1172 __PACKAGE__->register_method(
1173         method  => "generic_receive",
1174         api_name        => "open-ils.circ.checkin.override",
1175         signature       => q/@see open-ils.circ.checkin/
1176 );
1177
1178 sub generic_receive {
1179         my( $self, $conn, $authtoken, $params ) = @_;
1180         my( $ctx, $requestor, $evt );
1181
1182         ( $requestor, $evt ) = $U->checkses($authtoken) if $__isrenewal;
1183         ( $requestor, $evt ) = $U->checksesperm( 
1184                 $authtoken, 'COPY_CHECKIN' ) unless $__isrenewal;
1185         return $evt if $evt;
1186
1187         # load up the circ objects
1188         if( !( $ctx = $params->{_ctx}) ) {
1189                 ( $ctx, $evt ) = create_circ_ctx( %$params, 
1190                         requestor                                               => $requestor, 
1191                         session                                                 => $U->start_db_session(),
1192                         type                                                            => 'circ',
1193                         fetch_copy_statuses                     => 1, 
1194                         fetch_copy_locations                    => 1, 
1195                         no_runner                                               => 1,  
1196                         );
1197                 return $evt if $evt;
1198         }
1199         $ctx->{override} = 1 if $self->api_name =~ /override/o;
1200         $ctx->{session} = $U->start_db_session() unless $ctx->{session};
1201         $ctx->{authtoken} = $authtoken;
1202         my $session = $ctx->{session};
1203
1204         my $copy = $ctx->{copy};
1205         $U->unflesh_copy($copy);
1206         return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') unless $copy;
1207
1208         $logger->info("Checkin copy called by user ".
1209                 $requestor->id." for copy ".$copy->id);
1210
1211         # ------------------------------------------------------------------------------
1212         # Update the patron penalty info in the DB
1213         # ------------------------------------------------------------------------------
1214         $U->update_patron_penalties( 
1215                 authtoken => $authtoken, 
1216                 patron    => $ctx->{patron},
1217                 background => 1
1218         );
1219
1220         return $self->checkin_do_receive($conn, $ctx);
1221 }
1222
1223 sub checkin_do_receive {
1224
1225         my( $self, $connection, $ctx ) = @_;
1226
1227         my $evt;
1228         my $copy                        = $ctx->{copy};
1229         my $session             = $ctx->{session};
1230         my $requestor   = $ctx->{requestor};
1231         my $change              = 0; # did we actually do anything?
1232         my $circ;
1233
1234         my @eventlist;
1235
1236         # does the copy have an attached alert message?
1237         my $ae = _check_copy_alert($copy);
1238         push(@eventlist, $ae) if $ae;
1239
1240         # is the copy is an a status we can't automatically resolve?
1241         $evt = _checkin_check_copy_status($ctx);
1242         push( @eventlist, $evt ) if $evt;
1243
1244
1245         # - see if the copy has an open circ attached
1246         #($ctx->{circ}, $evt)   = $U->fetch_open_circulation($copy->id);
1247         ($ctx->{circ}, $evt)    = $U->fetch_all_open_circulation($copy->id); # - get ones with stop fines as well
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, OpenILS::Event->new('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