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