]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
fixed logic error
[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
623         my @allevents; #= ( @evts_so_far, @$copy_events );
624         push( @allevents, OpenILS::Event->new($_)) for @evts_so_far;
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         my $val = $self->checkin_do_receive($conn, $ctx);
1213
1214         # ------------------------------------------------------------------------------
1215         # Update the patron penalty info in the DB
1216         # ------------------------------------------------------------------------------
1217         $U->update_patron_penalties( 
1218                 authtoken => $authtoken, 
1219                 patron    => $ctx->{patron},
1220                 background => 1
1221         );
1222
1223         return $val;
1224 }
1225
1226 sub checkin_do_receive {
1227
1228         my( $self, $connection, $ctx ) = @_;
1229
1230         my $evt;
1231         my $copy                        = $ctx->{copy};
1232         my $session             = $ctx->{session};
1233         my $requestor   = $ctx->{requestor};
1234         my $change              = 0; # did we actually do anything?
1235         my $circ;
1236
1237         my @eventlist;
1238
1239         # does the copy have an attached alert message?
1240         my $ae = _check_copy_alert($copy);
1241         push(@eventlist, $ae) if $ae;
1242
1243         # is the copy is an a status we can't automatically resolve?
1244         $evt = _checkin_check_copy_status($ctx);
1245         push( @eventlist, $evt ) if $evt;
1246
1247
1248         # - see if the copy has an open circ attached
1249         #($ctx->{circ}, $evt)   = $U->fetch_open_circulation($copy->id);
1250         ($ctx->{circ}, $evt)    = $U->fetch_all_open_circulation($copy->id); # - get ones with stop fines as well
1251         return $evt if ($evt and $__isrenewal); # renewals require a circulation
1252         $evt = undef;
1253         $circ = $ctx->{circ};
1254
1255         # if the circ is marked as 'claims returned', add the event to the list
1256         push( @eventlist, OpenILS::Event->new('CIRC_CLAIMS_RETURNED') ) 
1257                 if ($circ and $circ->stop_fines and $circ->stop_fines eq 'CLAIMSRETURNED');
1258
1259         # override or die
1260         if(@eventlist) {
1261                 if($ctx->{override}) {
1262                         $evt = override_events($requestor, $requestor->ws_ou, \@eventlist );
1263                         return $evt if $evt;
1264                 } else {
1265                         return \@eventlist;
1266                 }
1267         }
1268
1269         ($ctx->{transit})       = $U->fetch_open_transit_by_copy($copy->id);
1270
1271         if( $ctx->{circ} ) {
1272
1273                 # There is an open circ on this item, close it out.
1274                 $change = 1;
1275                 $evt            = _checkin_handle_circ($ctx);
1276                 return $evt if $evt;
1277
1278         } elsif( $ctx->{transit} ) {
1279
1280                 # is this item currently in transit?
1281                 $change                 = 1;
1282                 $evt                            = $transcode->transit_receive( $copy, $requestor, $session );
1283                 my $holdtrans   = $evt->{holdtransit};
1284                 ($ctx->{hold})  = $U->fetch_hold($holdtrans->hold) if $holdtrans;
1285
1286                 if( ! $U->event_equals($evt, 'SUCCESS') ) {
1287
1288                         # either an error occurred or a ROUTE_ITEM was generated and the 
1289                         # item must be forwarded on to its destination.
1290                         return _checkin_flesh_event($ctx, $evt);
1291
1292                 } else {
1293
1294                         # Transit has been closed, now let's see if the copy's original
1295                         # status is something the staff should be warned of
1296                         my $e = _checkin_check_copy_status($ctx);
1297                         $evt = $e if $e;
1298
1299                         if($holdtrans) {
1300
1301                                 # copy was received as a hold transit.  Copy is at target lib
1302                                 # and hold transit is complete.  We're done here...
1303                                 $U->commit_db_session($session);
1304                                 return _checkin_flesh_event($ctx, $evt);
1305                         }
1306                         $evt = undef;
1307                 }
1308         }
1309
1310         # ------------------------------------------------------------------------------
1311         # Circulations and transits are now closed where necessary.  Now go on to see if
1312         # this copy can fulfill a hold or needs to be routed to a different location
1313         # ------------------------------------------------------------------------------
1314
1315
1316         # If it's a renewal, we're done
1317         if($__isrenewal) {
1318                 #$$ctx{force} = 1;
1319                 #my ($cc, $ee) = _reshelve_copy($ctx);
1320                 #return $ee if $ee;
1321                 #delete $$ctx{force};
1322                 $U->commit_db_session($session);
1323                 return OpenILS::Event->new('SUCCESS');
1324         }
1325
1326         # Now, let's see if this copy is needed for a hold
1327         my ($hold) = $holdcode->find_local_hold( $session, $copy, $requestor ); 
1328
1329         if($hold) {
1330
1331                 $ctx->{hold}    = $hold;
1332                 $change                 = 1;
1333                 
1334                 # Capture the hold with this copy
1335                 return $evt if ($evt = _checkin_capture_hold($ctx));
1336
1337                 if( $hold->pickup_lib == $requestor->ws_ou ) {
1338
1339                         # This hold was captured in the correct location
1340                         $evt = OpenILS::Event->new('SUCCESS');
1341
1342                 } else {
1343
1344                         # Hold needs to be picked up elsewhere.  Build a hold 
1345                         # transit and route the item.
1346                         return $evt if ($evt =_checkin_build_hold_transit($ctx));
1347                         $evt = OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib);
1348                 }
1349
1350         } else { # not needed for a hold
1351
1352                 if( $copy->circ_lib == $requestor->ws_ou ) {
1353
1354                         # Copy is in the right place.
1355                         $evt = OpenILS::Event->new('SUCCESS');
1356
1357                         # if the item happens to be a pre-cataloged item, send it
1358                         # to cataloging and return the event
1359                         my( $e, $c, $err ) = _checkin_handle_precat($ctx);
1360                         return $err if $err;
1361                         $change         = 1 if $c;
1362                         $evt                    = $e if $e;
1363
1364                 } else {
1365
1366                         # Copy wants to go home. Transit it there.
1367                         return $evt if ( $evt = _checkin_build_generic_copy_transit($ctx) );
1368                         $evt                    = OpenILS::Event->new('ROUTE_ITEM', org => $copy->circ_lib);
1369                         $change         = 1;
1370                 }
1371         }
1372
1373
1374         # ------------------------------------------------------------------
1375         # if the copy is not in a state that should persist,
1376         # set the copy to reshelving if it's not already there
1377         # ------------------------------------------------------------------
1378         my ($c, $e) = _reshelve_copy($ctx);
1379         return $e if $e;
1380         $change = $c unless $change;
1381
1382         if(!$change) {
1383
1384                 $evt = OpenILS::Event->new('NO_CHANGE');
1385                 ($ctx->{hold}) = $U->fetch_open_hold_by_copy($copy->id) 
1386
1387                 # what is this?
1388                 if( $copy->status == $U->copy_status_from_name('on holds shelf')->id );
1389
1390         } else {
1391
1392                 $U->commit_db_session($session);
1393         }
1394
1395         $logger->activity("checkin by user ".$requestor->id." on item ".
1396                 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1397
1398         return _checkin_flesh_event($ctx, $evt);
1399 }
1400
1401 sub _reshelve_copy {
1402
1403         my $ctx = shift;
1404         my $copy                = $ctx->{copy};
1405         my $reqr                = $ctx->{requestor};
1406         my $session     = $ctx->{session};
1407         my $force       = $ctx->{force};
1408
1409         my $stat = ref($copy->status) ? $copy->status->id : $copy->status;
1410
1411         if($force || (
1412                 $stat != $U->copy_status_from_name('on holds shelf')->id and 
1413                 $stat != $U->copy_status_from_name('available')->id and 
1414                 $stat != $U->copy_status_from_name('cataloging')->id and 
1415                 $stat != $U->copy_status_from_name('in transit')->id and 
1416                 $stat != $U->copy_status_from_name('reshelving')->id) ) {
1417
1418                 $copy->status( $U->copy_status_from_name('reshelving')->id );
1419
1420                 my $evt = $U->update_copy( 
1421                         copy            => $copy,
1422                         editor  => $reqr->id,
1423                         session => $session,
1424                         );
1425
1426                 return( 1, $evt );
1427         }
1428         return undef;
1429 }
1430
1431
1432
1433
1434 # returns undef if there are no 'open' claims-returned circs attached
1435 # to the given copy.  if there is an open claims-returned circ, 
1436 # then we check for override mode.  if in override, mark the claims-returned
1437 # circ as checked in.  if not, return event.
1438 sub _handle_claims_returned {
1439         my $ctx = shift;
1440         my $copy = $ctx->{copy};
1441
1442         my $CR  = _fetch_open_claims_returned($copy->id);
1443         return undef unless $CR;
1444
1445         # - If the caller has set the override flag, we will check the item in
1446         if($ctx->{override}) {
1447
1448                 $CR->checkin_time('now');       
1449                 $CR->checkin_lib($ctx->{requestor}->ws_ou);
1450                 $CR->checkin_staff($ctx->{requestor}->id);
1451
1452                 my $stat = $U->storagereq(
1453                         'open-ils.storage.direct.action.circulation.update', $CR);
1454                 return $U->DB_UPDATE_FAILED($CR) unless $stat;
1455                 return OpenILS::Event->new('SUCCESS');
1456
1457         } else {
1458                 # - if not in override mode, return the CR event
1459                 return OpenILS::Event->new('CIRC_CLAIMS_RETURNED');
1460         }
1461 }
1462
1463
1464 sub _fetch_open_claims_returned {
1465         my $copyid = shift;
1466         my $trans = $U->storagereq(
1467                 'open-ils.storage.direct.action.circulation.search_where',
1468                 {       
1469                         target_copy             => $copyid, 
1470                         stop_fines              => 'CLAIMSRETURNED',
1471                         checkin_time    => undef,
1472                 }
1473         );
1474         return $$trans[0] if $trans && $$trans[0];
1475         return undef;
1476 }
1477
1478 # - if the copy is has the 'in process' status, set it to reshelving
1479 #sub _check_in_process {
1480         #my $ctx = shift;
1481 #
1482         #my $copy = $ctx->{copy};
1483         #my $reqr       = $ctx->{requestor};
1484         #my $ses        = $ctx->{session};
1485 ##
1486         #my $stat = $U->copy_status_from_name('in process');
1487         #my $rstat = $U->copy_status_from_name('reshelving');
1488 #
1489         #if( $stat->id == $copy->status->id ) {
1490                 #$logger->info("marking 'in-process' copy ".$copy->id." as 'reshelving'");
1491                 #$copy->status( $rstat->id );
1492                 #my $evt = $U->update_copy( 
1493                         #copy           => $copy,
1494                         #editor => $reqr->id,
1495                         #session        => $ses
1496                         #);
1497                 #return $evt if $evt;
1498 #
1499                 #$copy->status( $rstat ); # - reflesh the copy status
1500         #}
1501         #return undef;
1502 #}
1503
1504
1505 # returns (ITEM_NOT_CATALOGED, change_occurred, $error_event) where necessary
1506 sub _checkin_handle_precat {
1507
1508         my $ctx         = shift;
1509         my $copy                = $ctx->{copy};
1510         my $evt         = undef;
1511         my $errevt      = undef;
1512         my $change      = 0;
1513
1514         my $catstat = $U->copy_status_from_name('cataloging');
1515
1516         if( $ctx->{precat} ) {
1517
1518                 $evt = OpenILS::Event->new('ITEM_NOT_CATALOGED');
1519
1520                 if( $copy->status != $catstat->id ) {
1521                         $copy->status($catstat->id);
1522
1523                         return (undef, 0, $errevt) if (
1524                                 $errevt = $U->update_copy(
1525                                         copy            => $copy, 
1526                                         editor  => $ctx->{requestor}->id, 
1527                                         session => $ctx->{session} ));
1528                         $change = 1;
1529
1530                 }
1531         }
1532
1533         return ($evt, $change, undef);
1534 }
1535
1536
1537 # returns the appropriate event for the given copy status
1538 # if the copy is not in a 'special' status, undef is returned
1539 sub _checkin_check_copy_status {
1540         my $ctx = shift;
1541         my $copy = $ctx->{copy};
1542         my $reqr        = $ctx->{requestor};
1543         my $ses = $ctx->{session};
1544
1545         my $islost              = 0;
1546         my $ismissing   = 0;
1547         my $evt                 = undef;
1548
1549         my $status = ref($copy->status) ? $copy->status->id : $copy->status;
1550
1551         return undef 
1552                 if(     $status == $U->copy_status_from_name('available')->id           ||
1553                                 $status == $U->copy_status_from_name('checked out')->id ||
1554                                 $status == $U->copy_status_from_name('in process')->id  ||
1555                                 $status == $U->copy_status_from_name('in transit')->id  ||
1556                                 $status == $U->copy_status_from_name('reshelving')->id );
1557
1558         return OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy ) 
1559                 if( $status == $U->copy_status_from_name('lost')->id );
1560
1561         return OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy ) 
1562                 if( $status == $U->copy_status_from_name('missing')->id );
1563
1564         return OpenILS::Event->new('COPY_BAD_STATUS', payload => $copy );
1565
1566
1567
1568 #       my $rstat = $U->copy_status_from_name('reshelving');
1569 #       my $stat = (ref($copy->status)) ? $copy->status->id : $copy->status;
1570 #
1571 #       if( $stat == $U->copy_status_from_name('lost')->id ) {
1572 #               $islost = 1;
1573 #               $evt = OpenILS::Event->new('COPY_STATUS_LOST', payload => $copy );
1574 #
1575 #       } elsif( $stat == $U->copy_status_from_name('missing')->id) {
1576 #               $ismissing = 1;
1577 #               $evt = OpenILS::Event->new('COPY_STATUS_MISSING', payload => $copy );
1578 #       }
1579 #
1580 #       return (undef,$evt) if(!$ctx->{override});
1581 #
1582 #       # we're are now going to attempt to override the failure 
1583 #       # and set the copy to reshelving
1584 #       my $e;
1585 #       my $copyid = $copy->id;
1586 #       my $userid = $reqr->id;
1587 #       if( $islost ) {
1588 #
1589 #               # - make sure we have permission
1590 #               $e = $U->check_perms( $reqr->id, 
1591 #                       $copy->circ_lib, 'COPY_STATUS_LOST.override');
1592 #               return (undef,$e) if $e;
1593 #               $copy->status( $rstat->id );
1594 #
1595 #               # XXX if no fines are owed in the circ, close it out - will this happen later anyway?
1596 #               #my $circ = $U->storagereq(
1597 #               #       'open-ils.storage.direct.action.circulation
1598 #
1599 #               $logger->activity("user $userid overriding 'lost' copy status for copy $copyid");
1600 #
1601 #       } elsif( $ismissing ) {
1602 #
1603 #               # - make sure we have permission
1604 #               $e = $U->check_perms( $reqr->id, 
1605 #                       $copy->circ_lib, 'COPY_STATUS_MISSING.override');
1606 #               return (undef,$e) if $e;
1607 #               $copy->status( $rstat->id );
1608 #               $logger->activity("user $userid overriding 'missing' copy status for copy $copyid");
1609 #       }
1610 #
1611 #       if( $islost or $ismissing ) {
1612 #
1613 #               # - update the copy with the new status
1614 #               $evt = $U->update_copy(
1615 #                       copy            => $copy,
1616 #                       editor  => $reqr->id,
1617 #                       session => $ses
1618 #               );
1619 #               return (undef,$evt) if $evt;
1620 #               $copy->status( $rstat );
1621 #       }
1622 #
1623 #       return (1);
1624
1625
1626 }
1627
1628 # Just gets the copy back home.  Returns undef on success, event on error
1629 sub _checkin_build_generic_copy_transit {
1630
1631         my $ctx                 = shift;
1632         my $requestor   = $ctx->{requestor};
1633         my $copy                        = $ctx->{copy};
1634         my $transit             = Fieldmapper::action::transit_copy->new;
1635         my $session             = $ctx->{session};
1636
1637         $logger->activity("User ". $requestor->id ." creating a ".
1638                 " new copy transit for copy ".$copy->id." to org ".$copy->circ_lib);
1639
1640         $transit->source($requestor->ws_ou);
1641         $transit->dest($copy->circ_lib);
1642         $transit->target_copy($copy->id);
1643         $transit->source_send_time('now');
1644         $transit->copy_status($copy->status);
1645         
1646         $logger->debug("Creating new copy_transit in DB");
1647
1648         my $s = $session->request(
1649                 "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
1650         return $U->DB_UPDATE_FAILED($transit) unless $s;
1651
1652         $logger->info("Checkin copy successfully created new transit: $s");
1653
1654         $copy->status($U->copy_status_from_name('in transit')->id );
1655
1656         return $U->update_copy( copy => $copy, 
1657                         editor => $requestor->id, session => $session );
1658         
1659 }
1660
1661
1662 # returns event on error, undef on success
1663 sub _checkin_build_hold_transit {
1664         my $ctx = shift;
1665
1666         my $copy = $ctx->{copy};
1667         my $hold = $ctx->{hold};
1668         my $trans = Fieldmapper::action::hold_transit_copy->new;
1669
1670         $trans->hold($hold->id);
1671         $trans->source($ctx->{requestor}->ws_ou);
1672         $trans->dest($hold->pickup_lib);
1673         $trans->source_send_time("now");
1674         $trans->target_copy($copy->id);
1675         $trans->copy_status($copy->status);
1676
1677         my $id = $ctx->{session}->request(
1678                 "open-ils.storage.direct.action.hold_transit_copy.create", $trans )->gather(1);
1679         return $U->DB_UPDATE_FAILED($trans) unless $id;
1680
1681         $logger->info("Checkin copy successfully created hold transit: $id");
1682
1683         $copy->status($U->copy_status_from_name('in transit')->id );
1684         return $U->update_copy( copy => $copy, 
1685                         editor => $ctx->{requestor}->id, session => $ctx->{session} );
1686 }
1687
1688 # Returns event on error, undef on success
1689 sub _checkin_capture_hold {
1690         my $ctx = shift;
1691         my $copy = $ctx->{copy};
1692         my $hold = $ctx->{hold}; 
1693
1694         $logger->debug("Checkin copy capturing hold ".$hold->id);
1695
1696         $hold->current_copy($copy->id);
1697         $hold->capture_time('now'); 
1698
1699         my $stat = $ctx->{session}->request(
1700                 "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1);
1701         return $U->DB_UPDATE_FAILED($hold) unless $stat;
1702
1703         $copy->status( $U->copy_status_from_name('on holds shelf')->id );
1704
1705         return $U->update_copy( copy => $copy, 
1706                         editor => $ctx->{requestor}->id, session => $ctx->{session} );
1707 }
1708
1709 # fleshes an event with the relevant objects from the context
1710 sub _checkin_flesh_event {
1711         my $ctx = shift;
1712         my $evt = shift;
1713
1714         my $payload                             = {};
1715         $payload->{copy}                = $U->unflesh_copy($ctx->{copy});
1716         $payload->{record}      = $U->record_to_mvr($ctx->{title}) if($ctx->{title} and !$ctx->{precat});
1717         $payload->{circ}                = $ctx->{circ} if $ctx->{circ};
1718         $payload->{transit}     = $ctx->{transit} if $ctx->{transit};
1719         $payload->{hold}                = $ctx->{hold} if $ctx->{hold};
1720
1721         $evt->{payload} = $payload;
1722         return $evt;
1723 }
1724
1725
1726 # Closes out the circulation, puts the copy into reshelving.
1727 # Voids any bills attached to this circ after the backdate time 
1728 # if a backdate is provided
1729 sub _checkin_handle_circ { 
1730
1731         my $ctx = shift;
1732
1733         my $circ = $ctx->{circ};
1734         my $copy = $ctx->{copy};
1735         my $requestor   = $ctx->{requestor};
1736         my $session             = $ctx->{session};
1737         my $evt;
1738         my $obt;
1739
1740         $logger->info("Handling circulation [".$circ->id."] found in checkin...");
1741
1742         # backdate the circ if necessary
1743         if(my $backdate = $ctx->{backdate}) {
1744                 return $evt if ($evt = 
1745                         _checkin_handle_backdate($backdate, $circ, $requestor, $session, 1));
1746         }
1747
1748
1749         if(!$circ->stop_fines) {
1750                 $circ->stop_fines('CHECKIN');
1751                 $circ->stop_fines('RENEW') if $__isrenewal;
1752                 $circ->stop_fines_time('now');
1753         }
1754
1755         # see if there are any fines owed on this circ.  if not, close it
1756         ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1757         return $evt if $evt;
1758         $circ->xact_finish('now') if( $obt->balance_owed == 0 );
1759
1760         # Set the checkin vars since we have the item
1761         $circ->checkin_time('now');
1762         $circ->checkin_staff($requestor->id);
1763         $circ->checkin_lib($requestor->ws_ou);
1764
1765         $evt = _set_copy_reshelving($copy, $requestor->id, $ctx->{session}); 
1766         return $evt if $evt;
1767
1768 #       $copy->status($U->copy_status_from_name('reshelving')->id);
1769 #       $evt = $U->update_copy( session => $session, 
1770 #               copy => $copy, editor => $requestor->id );
1771 #       return $evt if $evt;
1772
1773         $ctx->{session}->request(
1774                 'open-ils.storage.direct.action.circulation.update', $circ )->gather(1);
1775
1776         return undef;
1777 }
1778
1779 sub _set_copy_reshelving {
1780         my( $copy, $reqr, $session ) = @_;
1781
1782         $logger->info("Setting copy ".$copy->id." to reshelving");
1783         $copy->status($U->copy_status_from_name('reshelving')->id);
1784
1785         my $evt = $U->update_copy( 
1786                 session => $session, 
1787                 copy            => $copy, 
1788                 editor  => $reqr
1789                 );
1790         return $evt if $evt;
1791 }
1792
1793 # returns event on error, undef on success
1794 # This voids all bills attached to the given circulation that occurred
1795 # after the backdate 
1796 # THIS DOES NOT CLOSE THE CIRC if there are no more fines on the item
1797 sub _checkin_handle_backdate {
1798         my( $backdate, $circ, $requestor, $session, $closecirc ) = @_;
1799
1800         $logger->activity("User ".$requestor->id.
1801                 " backdating circ [".$circ->target_copy."] to date: $backdate");
1802
1803         my $bills = $session->request( # XXX Verify this call is correct
1804                 "open-ils.storage.direct.money.billing.search_where.atomic",
1805                 billing_ts => { ">=" => $backdate }, "xact" => $circ->id )->gather(1);
1806
1807         if($bills) {
1808                 for my $bill (@$bills) {
1809                         $bill->voided('t');
1810                         my $n = $bill->note || "";
1811                         $bill->note($n . "\nSYSTEM VOIDED FOR BACKDATE");
1812                         my $s = $session->request(
1813                                 "open-ils.storage.direct.money.billing.update", $bill)->gather(1);
1814                         return $U->DB_UPDATE_FAILED($bill) unless $s;
1815                 }
1816         }
1817
1818         # if the caller elects to attempt to close the circulation
1819         # transaction, then it will be closed if there are not further
1820         # charges on the transaction
1821         #if( $closecirc ) {
1822                 #my ( $obt, $evt ) = $U->fetch_open_billable_transaction($circ->id);
1823            #return $evt if $evt;
1824                 #$circ->xact_finish($backdate) if $obt->balance_owed <= 0;
1825         #}
1826
1827         return undef;
1828 }
1829
1830
1831 sub _find_patron_from_params {
1832         my $params = shift;
1833
1834         my $patron;
1835         my $copy;
1836         my $circ;
1837         my $evt;
1838
1839         if(my $barcode = $params->{barcode}) {
1840                 $logger->debug("circ finding user from params with barcode $barcode");
1841                 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
1842                 return (undef, undef, $evt) if $evt;
1843                 ($circ, $evt) = $U->fetch_open_circulation($copy->id);
1844                 return (undef, undef, $evt) if $evt;
1845                 ($patron, $evt) = $U->fetch_user($circ->usr);
1846                 return (undef, undef, $evt) if $evt;
1847         }
1848         return ($patron, $copy);
1849 }
1850
1851
1852 # ------------------------------------------------------------------------------
1853
1854 __PACKAGE__->register_method(
1855         method  => "renew",
1856         api_name        => "open-ils.circ.renew.override",
1857         signature       => q/@see open-ils.circ.renew/,
1858 );
1859
1860
1861 __PACKAGE__->register_method(
1862         method  => "renew",
1863         api_name        => "open-ils.circ.renew",
1864         notes           => <<"  NOTES");
1865         PARAMS( authtoken, circ => circ_id );
1866         open-ils.circ.renew(login_session, circ_object);
1867         Renews the provided circulation.  login_session is the requestor of the
1868         renewal and if the logged in user is not the same as circ->usr, then
1869         the logged in user must have RENEW_CIRC permissions.
1870         NOTES
1871
1872 sub renew {
1873         my( $self, $client, $authtoken, $params ) = @_;
1874         $U->logmark;
1875
1876         my ( $requestor, $patron, $ctx, $evt, $circ, $copy );
1877         $__isrenewal = 1;
1878
1879         $params->{override} = 1 if $self->api_name =~ /override/o;
1880
1881         # fetch the patron object one way or another
1882         if( $params->{patron} ) {
1883                 ( $patron, $evt ) = $U->fetch_user($params->{patron});
1884                 if($evt) { $__isrenewal = 0; return $evt; }
1885
1886         } elsif( $params->{patron_barcode} ) {
1887                 ( $patron, $evt ) = $U->fetch_user_by_barcode($params->{patron_barcode});
1888                 if($evt) { $__isrenewal = 0; return $evt; }
1889
1890         } else {
1891                 ($patron, $copy, $evt) = _find_patron_from_params($params);
1892                 return $evt if $evt;
1893                 $params->{copy} = $copy;
1894         }
1895
1896         # verify our login session
1897         ($requestor, $evt) = $U->checkses($authtoken);
1898         if($evt) { $__isrenewal = 0; return $evt; }
1899
1900         # make sure we have permission to perform a renewal
1901         if( $requestor->id ne $patron->id ) {
1902                 $evt = $U->check_perms($requestor->id, $requestor->ws_ou, 'RENEW_CIRC');
1903                 if($evt) { $__isrenewal = 0; return $evt; }
1904         }
1905
1906
1907         # fetch and build the circulation environment
1908         ( $ctx, $evt ) = create_circ_ctx( %$params, 
1909                 patron                                                  => $patron, 
1910                 requestor                                               => $requestor, 
1911                 patron                                                  => $patron, 
1912                 type                                                            => 'circ',
1913                 #fetch_patron_circ_summary      => 1,
1914                 fetch_copy_statuses                     => 1, 
1915                 fetch_copy_locations                    => 1, 
1916                 );
1917         if($evt) { $__isrenewal = 0; return $evt; }
1918         $params->{_ctx} = $ctx;
1919
1920         # make sure they have some renewals left and make sure the circulation exists
1921         ($circ, $evt) = _check_renewal_remaining($ctx);
1922         if($evt) { $__isrenewal = 0; return $evt; }
1923         $ctx->{old_circ} = $circ;
1924         my $renewals = $circ->renewal_remaining - 1;
1925
1926         # run the renew permit script
1927         $evt = _run_renew_scripts($ctx);
1928         if($evt) { $__isrenewal = 0; return $evt; }
1929
1930         # checkin the cop
1931         #$ctx->{patron} = $ctx->{patron}->id;
1932         $evt = $self->generic_receive($client, $authtoken, $ctx );
1933                 #{ barcode => $params->{barcode}, patron => $params->{patron}} );
1934
1935         if( !$U->event_equals($evt, 'SUCCESS') ) {
1936                 $__isrenewal = 0; return $evt; 
1937         }
1938
1939         # re-fetch the context since objects have changed in the checkin
1940         ( $ctx, $evt ) = create_circ_ctx( %$params, 
1941                 patron                                                  => $patron, 
1942                 requestor                                               => $requestor, 
1943                 patron                                                  => $patron, 
1944                 type                                                            => 'circ',
1945                 #fetch_patron_circ_summary      => 1,
1946                 fetch_copy_statuses                     => 1, 
1947                 fetch_copy_locations                    => 1, 
1948                 );
1949         if($evt) { $__isrenewal = 0; return $evt; }
1950         $params->{_ctx} = $ctx;
1951         $ctx->{renewal_remaining} = $renewals;
1952
1953         # run the circ permit scripts
1954         if( $ctx->{permit_override} ) {
1955                 $evt = $U->check_perms(
1956                         $requestor->id, $ctx->{copy}->circ_lib->id, 'CIRC_PERMIT_OVERRIDE');
1957                 if($evt) { $__isrenewal = 0; return $evt; }
1958
1959         } else {
1960                 $evt = $self->permit_circ( $client, $authtoken, $params );
1961                 if( $U->event_equals($evt, 'ITEM_NOT_CATALOGED')) {
1962                         #$ctx->{precat} = 1;
1963                         $params->{precat} = 1;
1964
1965                 } else {
1966                         if(!$U->event_equals($evt, 'SUCCESS')) {
1967                                 if($evt) { $__isrenewal = 0; return $evt; }
1968                         }
1969                 }
1970                 $params->{permit_key} = $evt->{payload};
1971         }
1972
1973
1974         # checkout the item again
1975         $params->{patron} = $ctx->{patron}->id;
1976         $evt = $self->checkout($client, $authtoken, $params );
1977
1978         $logger->activity("user ".$requestor->id." renewl of item ".
1979                 $ctx->{copy}->barcode." completed with event ".$evt->{textcode});
1980
1981         $__isrenewal = 0;
1982         return $evt;
1983 }
1984
1985 sub _check_renewal_remaining {
1986         my $ctx = shift;
1987         $U->logmark;
1988         my( $circ, $evt ) = $U->fetch_open_circulation($ctx->{copy}->id);
1989         return (undef, $evt) if $evt;
1990         $evt = OpenILS::Event->new(
1991                 'MAX_RENEWALS_REACHED') if $circ->renewal_remaining < 1;
1992         return ($circ, $evt);
1993 }
1994
1995 sub _run_renew_scripts {
1996         my $ctx = shift;
1997         my $runner = $ctx->{runner};
1998         $U->logmark;
1999
2000         $runner->load($scripts{circ_permit_renew});
2001         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@");
2002
2003         my $events = $runner->retrieve('result.events');
2004         $events = [ split(/,/, $events) ]; 
2005         $logger->activity("circ_permit_renew for user ".
2006                 $ctx->{patron}->id." returned events: @$events") if @$events;
2007
2008         my @allevents;
2009         push( @allevents, OpenILS::Event->new($_)) for @$events;
2010         return \@allevents if  @allevents;
2011
2012         return undef;
2013 }
2014
2015         
2016
2017
2018 1;
2019